feat: make progress messages always silent

This commit is contained in:
banteg
2025-12-29 13:31:16 +04:00
parent d552954321
commit 0a0f206865
4 changed files with 61 additions and 45 deletions
+2 -1
View File
@@ -36,12 +36,13 @@ uv run exec-bridge
Optional flags: Optional flags:
- `--progress-edit-every FLOAT` (default `2.0`) - `--progress-edit-every FLOAT` (default `2.0`)
- `--progress-silent/--no-progress-silent` (default silent)
- `--final-notify/--no-final-notify` (default notify via new message) - `--final-notify/--no-final-notify` (default notify via new message)
- `--ignore-backlog/--process-backlog` (default ignore pending updates) - `--ignore-backlog/--process-backlog` (default ignore pending updates)
- `--cd PATH` (pass through to `codex --cd`) - `--cd PATH` (pass through to `codex --cd`)
- `--model NAME` (pass through to `codex exec`) - `--model NAME` (pass through to `codex exec`)
Progress updates are always sent silently.
To resume an existing thread without a database, reply with (or include) the session id shown at the end of the bot response: To resume an existing thread without a database, reply with (or include) the session id shown at the end of the bot response:
`resume: \`019b66fc-64c2-7a71-81cd-081c504cfeb2\`` `resume: \`019b66fc-64c2-7a71-81cd-081c504cfeb2\``
@@ -8,12 +8,10 @@ import os
import re import re
import shlex import shlex
import shutil import shutil
import sys
import time import time
from collections import deque from collections import deque
from collections.abc import Awaitable, Callable from collections.abc import Awaitable, Callable
from dataclasses import dataclass from dataclasses import dataclass
from logging.handlers import RotatingFileHandler
from typing import Any from typing import Any
import typer import typer
@@ -21,6 +19,7 @@ import typer
from .config import load_telegram_config from .config import load_telegram_config
from .constants import TELEGRAM_HARD_LIMIT from .constants import TELEGRAM_HARD_LIMIT
from .exec_render import ExecProgressRenderer, render_event_cli from .exec_render import ExecProgressRenderer, render_event_cli
from .logging import setup_logging
from .rendering import render_markdown from .rendering import render_markdown
from .telegram_client import TelegramClient from .telegram_client import TelegramClient
@@ -53,33 +52,6 @@ async def _drain_stderr(stderr: asyncio.StreamReader | None, tail: deque[str]) -
logger.debug("[codex][stderr] drain error: %s", e) logger.debug("[codex][stderr] drain error: %s", e)
def setup_logging(log_file: str | None, *, debug: bool = False) -> None:
root_logger = logging.getLogger()
root_logger.setLevel(logging.DEBUG)
for handler in root_logger.handlers[:]:
root_logger.removeHandler(handler)
handler.close()
fmt = logging.Formatter("%(asctime)s %(levelname)s %(name)s: %(message)s")
console = logging.StreamHandler(sys.stdout)
console.setLevel(logging.DEBUG if debug else logging.INFO)
console.setFormatter(fmt)
root_logger.addHandler(console)
if log_file:
file_handler = RotatingFileHandler(
log_file,
maxBytes=5 * 1024 * 1024,
backupCount=3,
encoding="utf-8",
)
file_handler.setLevel(logging.DEBUG if debug else logging.INFO)
file_handler.setFormatter(fmt)
root_logger.addHandler(file_handler)
logger.debug("[debug] file logger initialized path=%r", log_file)
TELEGRAM_TEXT_LIMIT = TELEGRAM_HARD_LIMIT TELEGRAM_TEXT_LIMIT = TELEGRAM_HARD_LIMIT
TELEGRAM_MARKDOWN_LIMIT = 3500 TELEGRAM_MARKDOWN_LIMIT = 3500
@@ -338,7 +310,6 @@ class BridgeConfig:
chat_id: int chat_id: int
ignore_backlog: bool ignore_backlog: bool
progress_edit_every_s: float progress_edit_every_s: float
progress_silent: bool
final_notify: bool final_notify: bool
startup_msg: str startup_msg: str
max_concurrency: int max_concurrency: int
@@ -347,7 +318,6 @@ class BridgeConfig:
def _parse_bridge_config( def _parse_bridge_config(
*, *,
progress_edit_every_s: float, progress_edit_every_s: float,
progress_silent: bool,
final_notify: bool, final_notify: bool,
ignore_backlog: bool, ignore_backlog: bool,
cd: str | None, cd: str | None,
@@ -398,7 +368,6 @@ def _parse_bridge_config(
chat_id=chat_id, chat_id=chat_id,
ignore_backlog=bool(ignore_backlog), ignore_backlog=bool(ignore_backlog),
progress_edit_every_s=progress_edit_every_s, progress_edit_every_s=progress_edit_every_s,
progress_silent=progress_silent,
final_notify=final_notify, final_notify=final_notify,
startup_msg=startup_msg, startup_msg=startup_msg,
max_concurrency=16, max_concurrency=16,
@@ -500,7 +469,7 @@ async def _handle_message(
text=initial_rendered, text=initial_rendered,
entities=initial_entities, entities=initial_entities,
reply_to_message_id=user_msg_id, reply_to_message_id=user_msg_id,
disable_notification=cfg.progress_silent, disable_notification=True,
) )
progress_id = int(progress_msg["message_id"]) progress_id = int(progress_msg["message_id"])
last_edit_at = time.monotonic() last_edit_at = time.monotonic()
@@ -556,7 +525,7 @@ async def _handle_message(
chat_id=chat_id, chat_id=chat_id,
text=err, text=err,
reply_to_message_id=user_msg_id, reply_to_message_id=user_msg_id,
disable_notification=cfg.progress_silent, disable_notification=True,
) )
return return
@@ -683,11 +652,6 @@ def run(
help="Minimum seconds between progress message edits.", help="Minimum seconds between progress message edits.",
min=1.0, min=1.0,
), ),
progress_silent: bool = typer.Option(
True,
"--progress-silent/--no-progress-silent",
help="Send the progress message without sound/vibration.",
),
final_notify: bool = typer.Option( final_notify: bool = typer.Option(
True, True,
"--final-notify/--no-final-notify", "--final-notify/--no-final-notify",
@@ -704,9 +668,9 @@ def run(
help="Log codex JSONL, Telegram requests, and rendered messages.", help="Log codex JSONL, Telegram requests, and rendered messages.",
), ),
log_file: str | None = typer.Option( log_file: str | None = typer.Option(
"exec_bridge.log", None,
"--log-file", "--log-file",
help="Write detailed debug logs to this file (set to empty to disable).", help="Write detailed logs to this file.",
), ),
cd: str | None = typer.Option( cd: str | None = typer.Option(
None, None,
@@ -722,7 +686,6 @@ def run(
setup_logging(log_file if log_file else None, debug=debug) setup_logging(log_file if log_file else None, debug=debug)
cfg = _parse_bridge_config( cfg = _parse_bridge_config(
progress_edit_every_s=progress_edit_every_s, progress_edit_every_s=progress_edit_every_s,
progress_silent=progress_silent,
final_notify=final_notify, final_notify=final_notify,
ignore_backlog=ignore_backlog, ignore_backlog=ignore_backlog,
cd=cd, cd=cd,
@@ -0,0 +1,54 @@
from __future__ import annotations
import logging
import re
import sys
from logging.handlers import RotatingFileHandler
TELEGRAM_TOKEN_RE = re.compile(r"bot\d+:[A-Za-z0-9_-]+")
TELEGRAM_BARE_TOKEN_RE = re.compile(r"\b\d+:[A-Za-z0-9_-]{10,}\b")
class RedactTokenFilter(logging.Filter):
def filter(self, record: logging.LogRecord) -> bool:
try:
message = record.getMessage()
except Exception:
return True
redacted = TELEGRAM_TOKEN_RE.sub("bot[REDACTED]", message)
redacted = TELEGRAM_BARE_TOKEN_RE.sub("[REDACTED_TOKEN]", redacted)
if redacted != message:
record.msg = redacted
record.args = ()
return True
def setup_logging(log_file: str | None, *, debug: bool = False) -> None:
root_logger = logging.getLogger()
root_logger.setLevel(logging.DEBUG)
for handler in root_logger.handlers[:]:
root_logger.removeHandler(handler)
handler.close()
fmt = logging.Formatter("%(asctime)s %(levelname)s %(name)s: %(message)s")
redactor = RedactTokenFilter()
console = logging.StreamHandler(sys.stdout)
console.setLevel(logging.DEBUG if debug else logging.INFO)
console.setFormatter(fmt)
console.addFilter(redactor)
root_logger.addHandler(console)
if log_file:
file_handler = RotatingFileHandler(
log_file,
maxBytes=5 * 1024 * 1024,
backupCount=3,
encoding="utf-8",
)
file_handler.setLevel(logging.DEBUG if debug else logging.INFO)
file_handler.setFormatter(fmt)
file_handler.addFilter(redactor)
root_logger.addHandler(file_handler)
logging.getLogger(__name__).debug("[debug] file logger initialized path=%r", log_file)
@@ -98,7 +98,6 @@ def test_final_notify_sends_loud_final_message() -> None:
chat_id=123, chat_id=123,
ignore_backlog=True, ignore_backlog=True,
progress_edit_every_s=999.0, progress_edit_every_s=999.0,
progress_silent=True,
final_notify=True, final_notify=True,
startup_msg="", startup_msg="",
max_concurrency=1, max_concurrency=1,
@@ -131,7 +130,6 @@ def test_new_final_message_forces_notification_when_too_long_to_edit() -> None:
chat_id=123, chat_id=123,
ignore_backlog=True, ignore_backlog=True,
progress_edit_every_s=999.0, progress_edit_every_s=999.0,
progress_silent=True,
final_notify=False, final_notify=False,
startup_msg="", startup_msg="",
max_concurrency=1, max_concurrency=1,