Раздел: App
Идентификатор:notifications· Экран:ui/home/NotificationSettingsScreen.kt
Снимок: 2026-10-11
1. Зачем и что это
Ключевая особенность: если приложение на переднем плане, уведомление не отправляется вовсе — вместо него звучит системный рингтон уведомления (CodexLifecycle.kt:174). То есть экран управляет именно фоновыми сигналами, а не самой реакцией на завершение хода.
В отличие от большинства экранов хаба, этот использует обычный TopAppBar с заголовком «Notifications», а не терминальную шапку.
2. Как попасть
Хаб инструментов → секция App → карточка «Уведомления» (hub_notifications_title, подпись «Какие агенты могут присылать уведомления», тайл на главной — «Сигналы»).
| Откуда ещё | Путь |
|---|---|
| Главная | тайл «Сигналы» (если не скрыт в Настройках главной) |
| Мост | app_navigate с screen: "notifications" |
Маршрут: Screen.kt:153 ("notifications" -> Screen.Notifications) → MainRouter.kt:190 (NotificationSettingsScreen(onBack = …)).
3. Экран: элементы и действия
| Элемент | Что делает | Где в коде |
|---|---|---|
TopAppBar «Notifications» | Заголовок, кнопка «назад» | NotificationSettingsScreen:56 |
| Секция «Per-agent notifications» | Подзаголовок SectionHeader | item(key = "header") |
| Пояснение | «Enable or disable notifications when a task completes while the app is in background» | item(key = "desc") |
| Карточка Codex | Switch → PREF_NOTIF_CODEX; иконка Code | item(key = "codex") |
| Карточка OpenCode | Switch → PREF_NOTIF_OPENCODE; иконка SmartToy | item(key = "opencode") |
| Секция «Scheduler» | Подзаголовок второй секции | item(key = "scheduler-header") |
| Карточка Scheduled runs | Switch → PREF_NOTIF_SCHEDULER; иконка Schedule | item(key = "scheduler") |
Все карточки устроены одинаково (AgentNotifToggle): контейнер surfaceContainerLow, радиус 12.dp, бордер outlineVariant с alpha 0.3, иконка 24.dp (primary при включении, onSurfaceVariant при выключенном), название titleSmall, описание bodySmall, Switch с checkedTrackColor = primary.
4. Возможности подробно
Проверка разрешения
Разрешение POST_NOTIFICATIONS запрашивается отдельно, автоматически, при первом запуске MainActivity.requestNotificationPermission():
- при
SDK < 33запрос не делается; - если разрешение уже выдано — выход;
- если ключ
notif_askedуже установлен — выход (повторно не спрашиваем); - иначе ключ выставляется до показа диалога и запускается
notificationPermissionLauncher.
Из-за этого порядка отказ пользователя записывается сразу, и приложение больше не спрашивает: чтобы вернуть запрос, нужно вручную выдать разрешение в настройках системы. Экран «Уведомления» не умеет инициировать запрос — только читать и переключать флаги.
Каналы уведомлений
Создаются лениво, через ensureChannel / ensureSchedulerChannel:
| Константа | id | Имя | Важность |
|---|---|---|---|
CHANNEL_CHAT | chat_updates | Chat updates | IMPORTANCE_DEFAULT |
CHANNEL_SCHEDULER | scheduler_runs | Scheduled runs | IMPORTANCE_DEFAULT |
Канал создаётся, только если его ещё нет, — то есть переименование или смена важности из системных настроек не перетирается приложением.
Уведомление агента
NotificationHelper.notifyChat(...) формирует PendingIntent на MainActivity с флагами FLAG_ACTIVITY_SINGLE_TOP or FLAG_ACTIVITY_CLEAR_TOP и extras:
| Extra | Смысл |
|---|---|
agentId, sessionId, title, agentType | базовые |
threadId | открыть нужный тред Codex (код передаёт только Codex) |
cwd, projectName | контекст проекта (передаёт Codex) |
screen + taskId | deep link на экран планировщика |
Само уведомление: маленькая иконка R.drawable.ic_tile, заголовок, текст с обрезанием до 200 символов в contentText и до 500 в BigTextStyle, setAutoCancel(true). Идентификатор уведомления — sessionId.hashCode() для чата и taskId.hashCode() для планировщика, поэтому на сессию приходит одно уведомление, а не пачка. Отмена — cancelChat(context, sessionId).
Кто вызывает
| Вызов | Тип агента |
|---|---|
ui/codex/CodexLifecycle.kt:178 | codex |
ui/opencode/ChatViewModel.kt:572 | opencode (дефолт) |
notifyScheduler | планировщик, флаг PREF_NOTIF_SCHEDULER |
Текст уведомления Codex собирается из длительности хода, токенов и статистики (R.string.codex_tokens), с запасным вариантом «Agent finished the turn».
Проверка флага
isEnabled(context, agentType) — единственная точка решения: для известных типов читается соответствующий ключ prefs, для любого другого типа возвращается true (то есть новый агент по умолчанию уведомления шлёт).
5. Данные: БД, файлы, настройки
| Что | Где лежит | Как меняется | Кто читает |
|---|---|---|---|
| Флаг Codex | SharedPreferences, notif_codex_enabled | Тап Switch | NotificationHelper.isEnabled |
| Флаг OpenCode | notif_opencode_enabled | Тап Switch | То же |
| Флаг планировщика | notif_scheduler_enabled | Тап Switch | notifyScheduler |
| Флаг «спросили» | notif_asked | MainActivity.requestNotificationPermission | Там же |
Дефолт у всех четырёх — true: уведомления включены из коробки. Синхронизации с сервером нет.
6. Backend: REST/роуты
Неприменимо: уведомления формируются на устройстве.
7. Мост и MCP
| Что | Как | Примечание |
|---|---|---|
| Открыть экран | app_navigate с screen: "notifications" | BridgeScreenCatalog.kt |
Отдельной команды для чтения/смены флагов уведомлений в коде моста не найдено.
8. Настройки, конфиг, квоты и лимиты
| Параметр | Значение | Где задаётся |
|---|---|---|
| Каналы | chat_updates, scheduler_runs | NotificationHelper |
| Обрезка текста | 200 символов / 500 в BigText | NotificationHelper |
| Число источников | 4 (3 агента + планировщик) | Экран |
| Минимальный SDK для runtime-разрешения | 33 (Android 13) | MainActivity |
9. Ловушки и ограничения
- Все строки экрана захардкожены по-английски («Notifications», «Per-agent notifications», «Codex», «Scheduled runs» и т. д.) — файл не локализован, в отличие от большинства экранов хаба. При русском интерфейсе экран останется английским.
- Тумблер не даёт разрешение. Включённый флаг при отсутствии
POST_NOTIFICATIONSничего не изменит — уведомлений не будет. - Повторный запрос невозможен из приложения. Ключ
notif_askedставится до показа диалога. - На переднем плане уведомление не приходит — только рингтон. Если кажется, что уведомления «сломались», сначала проверьте, не было ли приложение открыто.
- Экран не показывает состояние разрешения системы — только свои флаги, что вводит в заблуждение.
- Тип агента вне списка всегда
true:isEnabledдля неизвестного значения возвращаетtrue, поэтому забытый агент будет присылать уведомления.
10. Источники правды
app/src/main/java/com/y83/ws/ui/home/NotificationSettingsScreen.kt— экран.app/src/main/java/com/y83/ws/util/NotificationHelper.kt— каналы, extras, отправка.app/src/main/java/com/y83/ws/MainActivity.kt— запрос разрешения, deep link.app/src/main/java/com/y83/ws/ui/codex/CodexLifecycle.kt— вызов при завершении хода.app/src/main/java/com/y83/ws/ui/opencode/ChatViewModel.kt— вызов агента.app/src/main/java/com/y83/ws/App.kt— ключиPREF_NOTIF_*.