Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
bdb5ea9
feat: add systemd service restart script
LikunYDev Feb 21, 2026
6b6374c
feat: add /context command to show context window usage
LikunYDev Feb 21, 2026
17ff3ee
fix: resolve multi-session concurrency bottlenecks
LikunYDev Feb 21, 2026
bfa9a43
fix: reduce Telegram API call rate to prevent 429 flood control
LikunYDev Feb 21, 2026
d80862b
Revert "fix: resolve multi-session concurrency bottlenecks"
LikunYDev Feb 21, 2026
0a55f35
fix: handle old state.json format and update restart script
LikunYDev Feb 21, 2026
41205f9
fix: add SHOW_TOOLS config and status dedup to reduce Telegram API calls
LikunYDev Feb 21, 2026
a63f36d
Merge branch 'fix/multi-session-concurrency'
LikunYDev Feb 21, 2026
faf286d
fix: support numbered ExitPlanMode selector and defer JSONL-based int…
LikunYDev Feb 28, 2026
f29e8c8
fix: auto-dismiss feedback survey, silent topic probe, and status ded…
LikunYDev Mar 1, 2026
9fd58ab
fix: thread-aware message queue, interactive UI fallback, and numbere…
LikunYDev Apr 1, 2026
0643bfe
fix: prevent duplicate AskUserQuestion/ExitPlanMode UI in Telegram
LikunYDev Apr 1, 2026
c5aef0b
Merge pull request #1 from LikunYDev/fix/duplicate-interactive-ui
LikunYDev Apr 2, 2026
de1f650
Merge remote-tracking branch 'origin/main'
LikunYDev Apr 15, 2026
a5a3bb8
docs: add macOS launchd service workflow
LikunYDev Apr 15, 2026
5425661
Revert "docs: add macOS launchd service workflow"
LikunYDev Apr 15, 2026
412b7b6
revert: drop service restart script changes from PR
LikunYDev Apr 15, 2026
e71e9b1
chore: remove unused imports and apply ruff format
LikunYDev Apr 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 33 additions & 5 deletions src/ccbot/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@
"help": "↗ Show Claude Code help",
"memory": "↗ Edit CLAUDE.md",
"model": "↗ Switch AI model",
"context": "↗ Show context window usage",
}


Expand Down Expand Up @@ -1741,12 +1742,17 @@ async def handle_new_message(msg: NewMessage, bot: Bot) -> None:
# Mark interactive mode BEFORE sleeping so polling skips this window
set_interactive_mode(user_id, wid, thread_id)
# Flush pending messages (e.g. plan content) before sending interactive UI
queue = get_message_queue(user_id)
queue = get_message_queue(user_id, thread_id)
if queue:
await queue.join()
# Wait briefly for Claude Code to render the question UI
await asyncio.sleep(0.3)
handled = await handle_interactive_ui(bot, user_id, wid, thread_id)
# Retry pane capture with increasing delays — Claude Code may need
# time to render the interactive UI after the JSONL entry is written.
handled = False
for delay in (0.3, 0.7, 1.0):
await asyncio.sleep(delay)
handled = await handle_interactive_ui(bot, user_id, wid, thread_id)
if handled:
break
if handled:
# Update user's read offset
session = await session_manager.resolve_session_for_window(wid)
Expand All @@ -1768,7 +1774,10 @@ async def handle_new_message(msg: NewMessage, bot: Bot) -> None:
await clear_interactive_msg(user_id, bot, thread_id)

# Skip tool call notifications when CCBOT_SHOW_TOOL_CALLS=false
if not config.show_tool_calls and msg.content_type in ("tool_use", "tool_result"):
if not config.show_tool_calls and msg.content_type in (
"tool_use",
"tool_result",
):
continue

parts = build_response_parts(
Expand Down Expand Up @@ -1841,6 +1850,25 @@ async def post_init(application: Application) -> None:
if rate_limiter and rate_limiter._base_limiter:
rate_limiter._base_limiter._level = rate_limiter._base_limiter.max_rate
logger.info("Pre-filled global rate limiter bucket")
# Also pre-fill per-group limiters for known chat IDs.
# Without this, the group limiter allows a burst of 20 requests on restart,
# which can exceed Telegram's persisted server-side per-group counter.
if hasattr(rate_limiter, "_group_limiters"):
from aiolimiter import AsyncLimiter

group_rate = getattr(rate_limiter, "_group_max_rate", 20)
group_period = getattr(rate_limiter, "_group_time_period", 60)
seen_chat_ids: set[int] = set()
for chat_id in session_manager.group_chat_ids.values():
if chat_id < 0 and chat_id not in seen_chat_ids:
seen_chat_ids.add(chat_id)
limiter = AsyncLimiter(group_rate, group_period)
limiter._level = limiter.max_rate
rate_limiter._group_limiters[chat_id] = limiter
if seen_chat_ids:
logger.info(
"Pre-filled %d group rate limiter bucket(s)", len(seen_chat_ids)
)

monitor = SessionMonitor()

Expand Down
16 changes: 16 additions & 0 deletions src/ccbot/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,22 @@ def __init__(self) -> None:
for var in SENSITIVE_ENV_VARS:
os.environ.pop(var, None)

# Display thinking messages in real-time and history
# Set SHOW_THINKING=false to skip thinking blocks (reduces Telegram API calls)
self.show_thinking = os.getenv("SHOW_THINKING", "true").lower() in (
"true",
"1",
"yes",
)

# Display tool_use/tool_result messages in real-time and history
# Set SHOW_TOOLS=false to skip tool messages (reduces Telegram API calls)
self.show_tools = os.getenv("SHOW_TOOLS", "true").lower() in (
"true",
"1",
"yes",
)

logger.debug(
"Config initialized: dir=%s, token=%s..., allowed_users=%d, "
"tmux_session=%s, claude_projects_path=%s",
Expand Down
10 changes: 10 additions & 0 deletions src/ccbot/handlers/history.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,16 @@ async def send_history(

lines = [header]
for msg in messages:
# Skip thinking messages unless show_thinking is enabled
if msg.get("content_type") == "thinking" and not config.show_thinking:
continue
# Skip tool messages unless show_tools is enabled
if (
msg.get("content_type") in ("tool_use", "tool_result")
and not config.show_tools
):
continue

# Format timestamp as HH:MM
ts = msg.get("timestamp")
if ts:
Expand Down
Loading
Loading