Merge pull request #24 from living-ip/codex/leo-telegram-x402-quote-resume-20260630
Some checks are pending
CI / lint-and-test (push) Waiting to run

Enable quote-first Leo Telegram research
This commit is contained in:
twentyOne2x 2026-06-30 19:13:33 +02:00 committed by GitHub
commit 06a8b547ab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 66 additions and 2 deletions

View file

@ -19,6 +19,11 @@ domain_expertise: >
# ─── Hosted Leo Runtime ────────────────────────────────────────────────── # ─── Hosted Leo Runtime ──────────────────────────────────────────────────
http_chat_proxy_url: "https://leo.livingip.xyz/api/agents/leo/chat" http_chat_proxy_url: "https://leo.livingip.xyz/api/agents/leo/chat"
http_research_proxy_url: "https://leo.livingip.xyz/api/agents/leo/research"
smart_research_command_prefixes:
- "/smart_research"
- "/paid_research"
auto_smart_research_from_chat: true
respond_to_private_chats: true respond_to_private_chats: true
# ─── KB Scope ──────────────────────────────────────────────────────────── # ─── KB Scope ────────────────────────────────────────────────────────────
@ -44,6 +49,10 @@ voice_definition: |
answer from retained Living IP runtime evidence and current route state. answer from retained Living IP runtime evidence and current route state.
Do not claim payment execution unless the HTTP route returns retained Do not claim payment execution unless the HTTP route returns retained
payment/readback evidence. payment/readback evidence.
When addressed or used in private chat, clear requests for fresh sourced
research should go through Leo's hosted research route. First return a paid
research quote and checkout link. Only resume paid execution after a
work_order_id or payment receipt is present.
# ─── Learnings ─────────────────────────────────────────────────────────── # ─── Learnings ───────────────────────────────────────────────────────────
learnings_file: agents/leo/learnings.md learnings_file: agents/leo/learnings.md

View file

@ -61,6 +61,7 @@ from http_chat_proxy import (
extract_smart_research_goal, extract_smart_research_goal,
post_chat_proxy, post_chat_proxy,
should_attach_structured_market_context, should_attach_structured_market_context,
smart_research_payment_fields_for_message,
smart_research_command_names, smart_research_command_names,
) )
@ -1128,6 +1129,10 @@ async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
if len(text) < MIN_MESSAGE_LENGTH: if len(text) < MIN_MESSAGE_LENGTH:
return return
if AGENT_HTTP_RESEARCH_PROXY_URL and extract_paid_work_order_id(text):
await handle_tagged(update, context)
return
# Conversation window behavior depends on chat type (Rio: DMs vs groups) # Conversation window behavior depends on chat type (Rio: DMs vs groups)
# DMs: auto-respond (always 1-on-1, no false positives) # DMs: auto-respond (always 1-on-1, no false positives)
# Groups: silent context only (reply-to is the only follow-up trigger) # Groups: silent context only (reply-to is the only follow-up trigger)
@ -1207,7 +1212,10 @@ async def handle_tagged(update: Update, context: ContextTypes.DEFAULT_TYPE):
tuple(AGENT_MENTION_ALIASES), tuple(AGENT_MENTION_ALIASES),
) )
if AGENT_HTTP_RESEARCH_PROXY_URL and smart_research_goal: if AGENT_HTTP_RESEARCH_PROXY_URL and smart_research_goal:
payment_gate = _smart_research_payment_gate(msg.chat_id) payment_gate = smart_research_payment_fields_for_message(
paid_work_order_id=paid_work_order_id,
configured_payment_gate=_smart_research_payment_gate(msg.chat_id),
)
proxy_research_goal = smart_research_goal proxy_research_goal = smart_research_goal
if should_attach_structured_market_context(smart_research_goal): if should_attach_structured_market_context(smart_research_goal):
market_context, market_data_audit, market_duration, market_tokens = await _market_context_for_message( market_context, market_data_audit, market_duration, market_tokens = await _market_context_for_message(

View file

@ -184,6 +184,19 @@ def should_attach_structured_market_context(message: str) -> bool:
return bool(_MARKET_CONTEXT_RE.search(text)) return bool(_MARKET_CONTEXT_RE.search(text))
def smart_research_payment_fields_for_message(
*,
paid_work_order_id: str | None,
configured_payment_gate: dict[str, Any],
) -> dict[str, Any]:
"""Return paid-execution fields only for a settled work-order resume message."""
if not paid_work_order_id:
return {"allow_paid_execution": False}
if configured_payment_gate.get("allow_paid_execution") is True:
return configured_payment_gate
return {"allow_paid_execution": False}
def build_smart_research_proxy_payload( def build_smart_research_proxy_payload(
*, *,
research_goal: str, research_goal: str,

View file

@ -19,6 +19,7 @@ from http_chat_proxy import ( # noqa: E402
extract_chat_proxy_reply, extract_chat_proxy_reply,
extract_smart_research_goal, extract_smart_research_goal,
should_attach_structured_market_context, should_attach_structured_market_context,
smart_research_payment_fields_for_message,
smart_research_command_names, smart_research_command_names,
) )
from market_data import extract_market_data_tokens, format_price_context # noqa: E402 from market_data import extract_market_data_tokens, format_price_context # noqa: E402
@ -31,6 +32,9 @@ def test_leo_config_opts_into_http_chat_proxy_without_changing_default_agents():
assert leo.name == "Leo" assert leo.name == "Leo"
assert leo.http_chat_proxy_url == "https://leo.livingip.xyz/api/agents/leo/chat" assert leo.http_chat_proxy_url == "https://leo.livingip.xyz/api/agents/leo/chat"
assert leo.http_research_proxy_url == "https://leo.livingip.xyz/api/agents/leo/research"
assert "/smart_research" in leo.smart_research_command_prefixes
assert leo.auto_smart_research_from_chat is True
assert leo.respond_to_private_chats is True assert leo.respond_to_private_chats is True
assert "@teLEOhuman" in leo.mention_aliases assert "@teLEOhuman" in leo.mention_aliases
assert leo_wallet_test.name == "Leo Wallet Test" assert leo_wallet_test.name == "Leo Wallet Test"
@ -43,7 +47,6 @@ def test_leo_config_opts_into_http_chat_proxy_without_changing_default_agents():
assert "@lipleowallet0622183538bot" in leo_wallet_test.mention_aliases assert "@lipleowallet0622183538bot" in leo_wallet_test.mention_aliases
assert rio.http_chat_proxy_url is None assert rio.http_chat_proxy_url is None
assert rio.respond_to_private_chats is False assert rio.respond_to_private_chats is False
assert leo.auto_smart_research_from_chat is False
def test_invalid_http_chat_proxy_url_fails_closed(tmp_path): def test_invalid_http_chat_proxy_url_fails_closed(tmp_path):
@ -135,6 +138,37 @@ def test_smart_research_payload_can_resume_paid_work_order_without_secret_materi
assert "secret" not in str(payload).lower() assert "secret" not in str(payload).lower()
def test_smart_research_payment_fields_quote_first_even_when_gate_enabled():
configured_gate = {
"allow_paid_execution": True,
"approval_ref": "approval_ref_livingip_x402_20260622",
"max_amount_usd": 0.01,
}
payment_fields = smart_research_payment_fields_for_message(
paid_work_order_id=None,
configured_payment_gate=configured_gate,
)
assert payment_fields == {"allow_paid_execution": False}
assert "approval_ref" not in payment_fields
def test_smart_research_payment_fields_resume_uses_capped_gate():
configured_gate = {
"allow_paid_execution": True,
"approval_ref": "approval_ref_livingip_x402_20260622",
"max_amount_usd": 0.01,
}
payment_fields = smart_research_payment_fields_for_message(
paid_work_order_id="sponsored_work_orders:f951ccc6c7762ecba6f76cf6",
configured_payment_gate=configured_gate,
)
assert payment_fields == configured_gate
@pytest.mark.parametrize( @pytest.mark.parametrize(
("message", "expected"), ("message", "expected"),
[ [