From d2a8d716ba087bcf15c692ed5c2f0e8925a885bd Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 01:42:07 +0000 Subject: [PATCH] /holdingnews says when the provider is busy; Sharesight MCP from robdevops fork Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_014k4kywyN5dAo39izUPvxbw --- lib/features/holding_news.py | 9 ++++++--- lib/llm/base.py | 1 + lib/telegram/handlers.py | 3 +-- mcp_servers.json | 2 +- tests/test_features.py | 8 ++++++++ 5 files changed, 17 insertions(+), 6 deletions(-) diff --git a/lib/features/holding_news.py b/lib/features/holding_news.py index fe561e0..3d9831f 100644 --- a/lib/features/holding_news.py +++ b/lib/features/holding_news.py @@ -18,7 +18,7 @@ from .. import config from ..ask import ask from ..context import Ctx -from ..llm.base import is_busy +from ..llm.base import BUSY_REPLY, is_busy from ..llm.gate import Route from ..mcp.results import table_records from ..prompts import HOLDING_NEWS_PROMPT @@ -233,11 +233,14 @@ async def on_command(ctx: Ctx, msg: Message) -> None: return draft = Draft(ctx.bot, msg.chat_id) await draft.start() + busy: list[str] = [] try: - result = (await run_holding_news(ctx, only_username=username)).get(username) + result = (await run_holding_news(ctx, only_username=username, busy=busy)).get(username) finally: draft.stop() - if result: + if busy: + await asyncio.to_thread(save, await msg.reply_text(BUSY_REPLY)) + elif result: await send_holding_news(ctx, msg.chat_id, *result) else: await asyncio.to_thread( diff --git a/lib/llm/base.py b/lib/llm/base.py index 15f72da..6272d0a 100644 --- a/lib/llm/base.py +++ b/lib/llm/base.py @@ -9,6 +9,7 @@ from ..mcp.schema import ToolDef BUSY_STATUSES = (429, 503, 529) +BUSY_REPLY = "The AI provider is at capacity right now. Please try again in a few minutes." class Overloaded(RuntimeError): diff --git a/lib/telegram/handlers.py b/lib/telegram/handlers.py index ad131b6..b4f669e 100644 --- a/lib/telegram/handlers.py +++ b/lib/telegram/handlers.py @@ -17,7 +17,7 @@ from ..context import Ctx from ..features import dm_buttons, holding_news, movers, periods, post from ..history import compact, format_rows -from ..llm.base import is_busy +from ..llm.base import BUSY_REPLY, is_busy from ..llm.gate import Route, is_portfolio_question, route from ..msgtext import describe, sender_name from ..prompts import MOVERS_EXPLAIN, PRESET_PROMPT, chat_prompt, down_note @@ -30,7 +30,6 @@ log = logging.getLogger("bot") PORTFOLIO_ADMINS_ONLY = "Portfolio data is for admins only." -BUSY_REPLY = "The AI provider is at capacity right now. Please try again in a few minutes." FOLLOW_UP_WORDS = 12 # a message this short right after the bot's reply may be a follow-up to it FOLLOW_UP_SECONDS = 30 * 60 FOLLOW_UP_CHARS = 400 # of the bot's reply that the tool gate also reads diff --git a/mcp_servers.json b/mcp_servers.json index da29fe0..c4124a7 100644 --- a/mcp_servers.json +++ b/mcp_servers.json @@ -17,7 +17,7 @@ "sharesight": { "description": "Sharesight portfolios. Never ask the user for portfolio IDs or holdings: call list_portfolios first (it names every portfolio and its owner), then get_performance_report for the ones involved. A report's holdings list is exactly their current positions, with quantity, value and gains. When comparing portfolios or explaining a result, write 2-3 short paragraphs: each portfolio's return, then its tailwinds (biggest contributors, with numbers) and headwinds (biggest detractors), and what explains the gap.", "command": "npx", - "args": ["-y", "github:sryburn/sharesight-mcp"], + "args": ["-y", "github:robdevops/sharesight-mcp"], "env": { "SHARESIGHT_CLIENT_ID": "${SHARESIGHT_CLIENT_ID}", "SHARESIGHT_CLIENT_SECRET": "${SHARESIGHT_CLIENT_SECRET}" diff --git a/tests/test_features.py b/tests/test_features.py index d7b5120..dbf78dd 100644 --- a/tests/test_features.py +++ b/tests/test_features.py @@ -261,3 +261,11 @@ async def test_flushcache_command_is_admin_only_and_flushes_by_portfolio_and_per every = user_msg(ctx.bot, "/flushcache", user_id=5, mid=5) await commands.flush_cache(ctx, update_for(every), N(args=[])) assert "Flushed 4 cached periods (all portfolios)" in every.replies[0]["text"] + + +async def test_holdingnews_command_says_when_the_provider_is_busy(env, store): + ctx = holding_ctx(env, store, [Overloaded("at capacity")]) + m = user_msg(ctx.bot, "/holdingnews", chat_id=5, user_id=5) + m.from_user.username = "bob_llama" + await holding_news.on_command(ctx, m) + assert "at capacity" in m.replies[-1]["text"] and "No major news" not in m.replies[-1]["text"]