diff --git a/codex_telegram_bridge/readme.md b/codex_telegram_bridge/readme.md index 48ecc18..e87a960 100644 --- a/codex_telegram_bridge/readme.md +++ b/codex_telegram_bridge/readme.md @@ -36,12 +36,13 @@ uv run exec-bridge Optional flags: - `--progress-edit-every FLOAT` (default `2.0`) -- `--progress-silent/--no-progress-silent` (default silent) - `--final-notify/--no-final-notify` (default notify via new message) - `--ignore-backlog/--process-backlog` (default ignore pending updates) - `--cd PATH` (pass through to `codex --cd`) - `--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: `resume: \`019b66fc-64c2-7a71-81cd-081c504cfeb2\`` diff --git a/codex_telegram_bridge/src/codex_telegram_bridge/exec_bridge.py b/codex_telegram_bridge/src/codex_telegram_bridge/exec_bridge.py index 1b1833d..29a270c 100644 --- a/codex_telegram_bridge/src/codex_telegram_bridge/exec_bridge.py +++ b/codex_telegram_bridge/src/codex_telegram_bridge/exec_bridge.py @@ -8,12 +8,10 @@ import os import re import shlex import shutil -import sys import time from collections import deque from collections.abc import Awaitable, Callable from dataclasses import dataclass -from logging.handlers import RotatingFileHandler from typing import Any import typer @@ -21,6 +19,7 @@ import typer from .config import load_telegram_config from .constants import TELEGRAM_HARD_LIMIT from .exec_render import ExecProgressRenderer, render_event_cli +from .logging import setup_logging from .rendering import render_markdown 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) -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_MARKDOWN_LIMIT = 3500 @@ -338,7 +310,6 @@ class BridgeConfig: chat_id: int ignore_backlog: bool progress_edit_every_s: float - progress_silent: bool final_notify: bool startup_msg: str max_concurrency: int @@ -347,7 +318,6 @@ class BridgeConfig: def _parse_bridge_config( *, progress_edit_every_s: float, - progress_silent: bool, final_notify: bool, ignore_backlog: bool, cd: str | None, @@ -398,7 +368,6 @@ def _parse_bridge_config( chat_id=chat_id, ignore_backlog=bool(ignore_backlog), progress_edit_every_s=progress_edit_every_s, - progress_silent=progress_silent, final_notify=final_notify, startup_msg=startup_msg, max_concurrency=16, @@ -500,7 +469,7 @@ async def _handle_message( text=initial_rendered, entities=initial_entities, reply_to_message_id=user_msg_id, - disable_notification=cfg.progress_silent, + disable_notification=True, ) progress_id = int(progress_msg["message_id"]) last_edit_at = time.monotonic() @@ -556,7 +525,7 @@ async def _handle_message( chat_id=chat_id, text=err, reply_to_message_id=user_msg_id, - disable_notification=cfg.progress_silent, + disable_notification=True, ) return @@ -683,11 +652,6 @@ def run( help="Minimum seconds between progress message edits.", 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( True, "--final-notify/--no-final-notify", @@ -704,9 +668,9 @@ def run( help="Log codex JSONL, Telegram requests, and rendered messages.", ), log_file: str | None = typer.Option( - "exec_bridge.log", + None, "--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( None, @@ -722,7 +686,6 @@ def run( setup_logging(log_file if log_file else None, debug=debug) cfg = _parse_bridge_config( progress_edit_every_s=progress_edit_every_s, - progress_silent=progress_silent, final_notify=final_notify, ignore_backlog=ignore_backlog, cd=cd, diff --git a/codex_telegram_bridge/src/codex_telegram_bridge/logging.py b/codex_telegram_bridge/src/codex_telegram_bridge/logging.py new file mode 100644 index 0000000..534b982 --- /dev/null +++ b/codex_telegram_bridge/src/codex_telegram_bridge/logging.py @@ -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) diff --git a/codex_telegram_bridge/tests/test_exec_bridge.py b/codex_telegram_bridge/tests/test_exec_bridge.py index 4632012..9a195a5 100644 --- a/codex_telegram_bridge/tests/test_exec_bridge.py +++ b/codex_telegram_bridge/tests/test_exec_bridge.py @@ -98,7 +98,6 @@ def test_final_notify_sends_loud_final_message() -> None: chat_id=123, ignore_backlog=True, progress_edit_every_s=999.0, - progress_silent=True, final_notify=True, startup_msg="", max_concurrency=1, @@ -131,7 +130,6 @@ def test_new_final_message_forces_notification_when_too_long_to_edit() -> None: chat_id=123, ignore_backlog=True, progress_edit_every_s=999.0, - progress_silent=True, final_notify=False, startup_msg="", max_concurrency=1,