diff --git a/tui_gateway/_stdin_recovery.py b/tui_gateway/_stdin_recovery.py index e4149da831..add5e841df 100644 --- a/tui_gateway/_stdin_recovery.py +++ b/tui_gateway/_stdin_recovery.py @@ -87,8 +87,7 @@ def handle_spurious_eof(recovery_times: list[float], log_fn: object) -> bool: if len(recovery_times) > MAX_RECOVERIES_PER_MINUTE: log_fn( # type: ignore[operator] f"stdin spurious-EOF recovery rate exceeded " - f"({len(recovery_times)}/min, cap {MAX_RECOVERIES_PER_MINUTE})" - ) + f"({len(recovery_times)}/min, cap {MAX_RECOVERIES_PER_MINUTE})") return False log_fn(f"stdin spurious EOF (subprocess O_NONBLOCK flip), recovering: {diagnose_stdin_state()}") # type: ignore[operator] # Restore blocking mode on the shared description, and clear SO_RCVTIMEO too: a diff --git a/tui_gateway/billing_view.py b/tui_gateway/billing_view.py index 3be9bcad12..ad65d0a75f 100644 --- a/tui_gateway/billing_view.py +++ b/tui_gateway/billing_view.py @@ -22,8 +22,7 @@ def _wire_str(value): def _serialize_billing_error(exc) -> dict: """Map a BillingError into the result.error envelope the TUI branches on.""" from hermes_cli.nous_billing import ( - BillingRemoteSpendingRevoked, BillingScopeRequired, BillingSessionRevoked, BillingTransient, - ) + BillingRemoteSpendingRevoked, BillingScopeRequired, BillingSessionRevoked, BillingTransient) kind = "error" if isinstance(exc, BillingRemoteSpendingRevoked): kind = "remote_spending_revoked" @@ -46,8 +45,7 @@ def _serialize_billing_error(exc) -> dict: # actor-aware copy + route recovery without re-parsing the message). "actor": getattr(exc, "actor", None), "code": getattr(exc, "code", None), - "recovery": getattr(exc, "recovery", None), - } + "recovery": getattr(exc, "recovery", None)} def _serialize_payment_method(pm) -> dict | None: @@ -57,8 +55,7 @@ def _serialize_payment_method(pm) -> dict | None: return None if pm.kind == "card": return { - "kind": "card", "brand": pm.brand, "last4": pm.last4, "wallet": pm.wallet, - "resolved_via": pm.resolved_via, + "kind": "card", "brand": pm.brand, "last4": pm.last4, "wallet": pm.wallet, "resolved_via": pm.resolved_via } if pm.kind == "link": return {"kind": "link", "email": pm.email, "resolved_via": pm.resolved_via} @@ -73,16 +70,14 @@ def _serialize_auto_reload(ar, format_money) -> dict | None: if ar.card.kind == "distinct": card_out = { "kind": "distinct", "payment_method_id": ar.card.payment_method_id, - "brand": ar.card.brand, "last4": ar.card.last4, - } + "brand": ar.card.brand, "last4": ar.card.last4} else: card_out = {"kind": ar.card.kind} return { "enabled": ar.enabled, "threshold_usd": _wire_str(ar.threshold_usd), "threshold_display": format_money(ar.threshold_usd), "reload_to_usd": _wire_str(ar.reload_to_usd), - "reload_to_display": format_money(ar.reload_to_usd), "card": card_out, - } + "reload_to_display": format_money(ar.reload_to_usd), "card": card_out} def _serialize_billing_state(state) -> dict: @@ -98,8 +93,7 @@ def _serialize_billing_state(state) -> dict: # None/False on older NAS payloads; resolved_via is the resolution # rung for rung-gated surfaces (/subscription confirm). "display": state.card.display, - "resolved_via": state.card.resolved_via, - } + "resolved_via": state.card.resolved_via} monthly_cap = None if state.monthly_cap is not None: mc = state.monthly_cap @@ -107,8 +101,7 @@ def _serialize_billing_state(state) -> dict: "limit_usd": _wire_str(mc.limit_usd), "limit_display": format_money(mc.limit_usd), "spent_this_month_usd": _wire_str(mc.spent_this_month_usd), "spent_display": format_money(mc.spent_this_month_usd), - "is_default_ceiling": mc.is_default_ceiling, - } + "is_default_ceiling": mc.is_default_ceiling} return { "ok": True, "logged_in": state.logged_in, @@ -133,8 +126,7 @@ def _serialize_billing_state(state) -> dict: "error": state.error, # Shared two-bar dollar usage model so /topup matches /usage and # /subscription from one fetch; fail-open. - "usage": _usage_payload(state), - } + "usage": _usage_payload(state)} def _usage_payload(state) -> dict: @@ -156,8 +148,7 @@ def _serialize_usage_bar(bar) -> Optional[dict]: return { "kind": bar.kind, "remaining_display": _fmt_usd(bar.remaining_usd), "total_display": _fmt_usd(bar.total_usd), "spent_display": _fmt_usd(bar.spent_usd), - "pct_used": bar.pct_used, "fill_fraction": bar.fill_fraction, - } + "pct_used": bar.pct_used, "fill_fraction": bar.fill_fraction} def _serialize_usage_model(model) -> dict: @@ -180,8 +171,7 @@ def _serialize_usage_model(model) -> dict: "total_spendable_display": _usd(model.total_spendable_usd), "has_topup": model.has_topup, "plan_bar": _serialize_usage_bar(model.plan_bar), - "topup_bar": _serialize_usage_bar(model.topup_bar), - } + "topup_bar": _serialize_usage_bar(model.topup_bar)} def _serialize_subscription_state(state) -> dict: @@ -202,8 +192,7 @@ def _serialize_subscription_state(state) -> dict: "pending_downgrade_display": format_renews(c.pending_downgrade_at), "cancel_at_period_end": c.cancel_at_period_end, "cancellation_effective_at": c.cancellation_effective_at, - "cancellation_effective_display": format_renews(c.cancellation_effective_at), - } + "cancellation_effective_display": format_renews(c.cancellation_effective_at)} # Selectable catalog for the in-terminal tier picker; price is pre-formatted # ($X / $X.YY) so the TUI renders it directly. tiers = [ @@ -211,10 +200,8 @@ def _serialize_subscription_state(state) -> dict: "tier_id": t.tier_id, "name": t.name, "tier_order": t.tier_order, "dollars_per_month_display": format_money(t.dollars_per_month), "monthly_credits": _wire_str(t.monthly_credits), "is_current": t.is_current, - "is_enabled": t.is_enabled, - } - for t in state.tiers - ] + "is_enabled": t.is_enabled} + for t in state.tiers] return { "ok": True, "logged_in": state.logged_in, @@ -230,8 +217,7 @@ def _serialize_subscription_state(state) -> dict: "error": state.error, # Shared two-bar usage model (account-info is the only source with # top-up dollars); fail-open → {available:false}; lazy when logged out. - "usage": _usage_payload(state), - } + "usage": _usage_payload(state)} def _serialize_subscription_preview(p) -> dict: @@ -246,8 +232,7 @@ def _serialize_subscription_preview(p) -> dict: "target_tier_name": p.target_tier_name, "monthly_credits_delta": _wire_str(p.monthly_credits_delta), "amount_due_now_cents": p.amount_due_now_cents, - "effective_at": p.effective_at, - } + "effective_at": p.effective_at} def register(server) -> None: diff --git a/tui_gateway/change_watcher.py b/tui_gateway/change_watcher.py index 40620a67bc..90a7479428 100644 --- a/tui_gateway/change_watcher.py +++ b/tui_gateway/change_watcher.py @@ -27,8 +27,7 @@ def resolve_skin() -> dict: "banner_logo": skin.banner_logo, "banner_hero": skin.banner_hero, "tool_prefix": skin.tool_prefix, - "help_header": (skin.branding or {}).get("help_header", ""), - } + "help_header": (skin.branding or {}).get("help_header", "")} except Exception: return {} @@ -117,8 +116,7 @@ def _pet_changed_payload() -> dict: "slug": pet.slug, "displayName": pet.display_name, "scale": scale, - "spritesheetRevision": _pet_sheet_revision(pet.spritesheet), - } + "spritesheetRevision": _pet_sheet_revision(pet.spritesheet)} except Exception: # noqa: BLE001 - cosmetic, never break the watcher return {"enabled": False} @@ -133,9 +131,7 @@ def _sessions_sig(): cron runs (which never touch this gateway's transports) all move. Served sibling profile homes are probed too, else a routed profile's Bot Chat never refreshes.""" return _newest_mtime_ns( - root / name - for root in (_watcher_home(), *_served_profile_homes) - for name in ("state.db", "state.db-wal") + root / name for root in (_watcher_home(), *_served_profile_homes) for name in ("state.db", "state.db-wal") ) @@ -159,8 +155,7 @@ def _pairing_sig(): with contextlib.suppress(OSError): # Only the ledgers: _rate_limits.json moves on every unauthorized DM. entries += [ - e for e in root.iterdir() if e.name.endswith(("-pending.json", "-approved.json")) - ] + e for e in root.iterdir() if e.name.endswith(("-pending.json", "-approved.json"))] return _newest_mtime_ns(entries) @@ -195,8 +190,7 @@ _CHANGE_WATCHES: dict[str, tuple[float, Any, Any]] = { "platforms.changed": (2.0, _platforms_sig, lambda: {}), "pairing.changed": (2.0, _pairing_sig, lambda: {}), # 1s so a queued DM envelope reaches the Desktop's push-triggered drain fast. - "bot_relay.outbox.pending": (1.0, _bot_relay_outbox_sig, lambda: {}), -} + "bot_relay.outbox.pending": (1.0, _bot_relay_outbox_sig, lambda: {})} # state.db moves on every append of a streaming turn and gateway_state.json on # in-flight bookkeeping; the floor coalesces bursts to one broadcast per window, diff --git a/tui_gateway/event_replay.py b/tui_gateway/event_replay.py index a86f1505b7..8611ae817a 100644 --- a/tui_gateway/event_replay.py +++ b/tui_gateway/event_replay.py @@ -100,5 +100,4 @@ def replay_stats() -> dict: return { "sessions": len(_replay_buffers), "events": sum(len(b) for b in _replay_buffers.values()), - "max_per_session": _REPLAY_BUFFER_MAX, - } + "max_per_session": _REPLAY_BUFFER_MAX} diff --git a/tui_gateway/mcp_rpc_helpers.py b/tui_gateway/mcp_rpc_helpers.py index 41bfebe9b3..34b6e17ca0 100644 --- a/tui_gateway/mcp_rpc_helpers.py +++ b/tui_gateway/mcp_rpc_helpers.py @@ -43,5 +43,4 @@ def summarize_server(name: str, cfg: dict) -> Dict[str, Any]: "auth": auth, "oauth_tokens_present": _oauth_tokens_present(name) if auth == "oauth" else None, "enabled": cfg.get("enabled", True) is not False, - "tools": cfg.get("tools"), - } + "tools": cfg.get("tools")} diff --git a/tui_gateway/methods_bot_relay.py b/tui_gateway/methods_bot_relay.py index f4d25bfecd..6576c113a9 100644 --- a/tui_gateway/methods_bot_relay.py +++ b/tui_gateway/methods_bot_relay.py @@ -31,8 +31,7 @@ def _run_delivery(profile: str, tmp: str) -> subprocess.CompletedProcess: return subprocess.run( local_delivery_command(profile, tmp), capture_output=True, text=True, encoding="utf-8", - errors="replace", timeout=600, - ) + errors="replace", timeout=600) @method("bot_relay.roster.sync") @@ -143,8 +142,7 @@ def _(rid, params: dict, _root=_relay_root, _run=_run_delivery) -> dict: # compacts the over-threshold transcript first (no fresh session is # ever minted). Auth/quota/config classes never retry. from tools.bot_failure_reasons import ( - RETRY_NONE, classify_agent_error, retry_action, - ) + RETRY_NONE, classify_agent_error, retry_action) first_detail = (proc.stderr or proc.stdout or "").strip()[-500:] if retry_action(classify_agent_error(first_detail)) != RETRY_NONE: @@ -158,8 +156,7 @@ def _(rid, params: dict, _root=_relay_root, _run=_run_delivery) -> dict: detail = (proc.stderr or proc.stdout or "").strip()[-500:] return _err( rid, 5092, f"delivery turn failed: {detail or proc.returncode}", - data={"reason": classify_agent_error(detail)}, - ) + data={"reason": classify_agent_error(detail)}) return _ok(rid, {"reply": (proc.stdout or "").strip()}) except subprocess.TimeoutExpired: return _err(rid, 5093, "delivery turn timed out") @@ -185,8 +182,7 @@ def _(rid, params: dict, _root=_relay_root) -> dict: write_reply( _root(), envelope_id, reply=str(params.get("reply") or ""), - error=str(params.get("error") or ""), reason=str(params.get("reason") or ""), - ) + error=str(params.get("error") or ""), reason=str(params.get("reason") or "")) return _ok(rid, {"ok": True}) except ValueError as e: return _err(rid, 4094, str(e)) @@ -201,8 +197,7 @@ def register(server) -> None: server._LONG_HANDLERS = server._LONG_HANDLERS | methods_groups.LONG_HANDLERS for name in ( "get_hosted_room_service", "_WORKER_UNAVAILABLE", "_profile_name", "_requested_profile", - "_api_server_key", "_room_link_run_storage_durable", - ): + "_api_server_key", "_room_link_run_storage_durable"): setattr(server, name, getattr(methods_groups, name)) methods_groups.bind_server(server) methods_groups.register(server) diff --git a/tui_gateway/methods_browser.py b/tui_gateway/methods_browser.py index c06641f9b7..f161b3ad4c 100644 --- a/tui_gateway/methods_browser.py +++ b/tui_gateway/methods_browser.py @@ -49,8 +49,7 @@ def _is_default_local_cdp(parsed) -> bool: parsed.scheme in {"http", "ws"} and parsed.hostname in {"127.0.0.1", "localhost"} and port == 9222 - and parsed.path in {"", "/", "/json", "/json/version"} - ) + and parsed.path in {"", "/", "/json", "/json/version"}) def _cdp_http_reachable(parsed, timeout: float = 2.0) -> bool: @@ -87,8 +86,7 @@ def _launch_failure_hints(port: int, system: str) -> list[str]: else [ "No supported Chromium-family browser executable was found in this environment.", f"Install one or start a Chromium-family browser with --remote-debugging-port={port}, then retry /browser connect.", - ] - ) + ]) return [ *hint, "Browser not connected — start a Chromium-family browser with remote debugging and retry /browser connect", @@ -98,8 +96,7 @@ def _launch_failure_hints(port: int, system: str) -> list[str]: def _connect_local_default(port: int, system: str, announce) -> str | None: """Discover (or launch) the default local debug browser → its CDP URL, or None after announcing failure.""" from hermes_cli.browser_connect import ( - discover_local_cdp_url, find_free_debug_port, launch_chrome_debug, local_port_in_use, - ) + discover_local_cdp_url, find_free_debug_port, launch_chrome_debug, local_port_in_use) # Dual-stack discovery: when another app squats the IPv4 loopback on the debug # port, a browser bound there comes up on [::1] only. An IPv4-only probe misses @@ -114,8 +111,7 @@ def _connect_local_default(port: int, system: str, announce) -> str | None: announce( f"Port {port} is occupied by another application that isn't a CDP browser " "(an IDE debugger or dev server may be using it) — launching a debug browser " - f"on port {launch_port} instead..." - ) + f"on port {launch_port} instead...") else: announce("Chromium-family browser isn't running with remote debugging — attempting to launch...") launch = launch_chrome_debug(launch_port, system) diff --git a/tui_gateway/methods_browser_control.py b/tui_gateway/methods_browser_control.py index f42ad94ade..2c393610b3 100644 --- a/tui_gateway/methods_browser_control.py +++ b/tui_gateway/methods_browser_control.py @@ -18,8 +18,7 @@ import hashlib import logging from hermes_cli.dashboard_auth.ws_tickets import ( - INTERNAL_PROVIDER as _INTERNAL_PROVIDER, INTERNAL_USER_ID as _INTERNAL_USER_ID, -) + INTERNAL_PROVIDER as _INTERNAL_PROVIDER, INTERNAL_USER_ID as _INTERNAL_USER_ID) from .method_ctx import HandlerRegistry, bind_module @@ -65,8 +64,7 @@ def _broker_event_writer(transport: object, session_id: str): "jsonrpc": "2.0", "method": "event", "params": { "type": frame.get("method"), "session_id": session_id, "payload": frame.get("params"), - }, - }) + }}) except Exception: logger.exception( "browser controller event write failed session=%s frame=%s", session_id, frame.get("method") @@ -80,8 +78,7 @@ def _broker_event_writer(transport: object, session_id: str): def _controller_method( name: str, *, identity_message: str = _IDENTITY_REQUIRED, lookup_scope: bool = True, - missing_scope_message: str = _NO_CONTROLLER, precheck=None, -): + missing_scope_message: str = _NO_CONTROLLER, precheck=None): """Register a handler behind the shared fail-closed (4403) controller gates. Order: ``precheck(rid, params)`` (may return an error envelope) → caller holds a @@ -113,8 +110,7 @@ def _controller_method( if lookup_scope: scope = broker.scope_for_session( session_id=session_id, principal_id=_principal_digest(identity), - transport_family=_CLOUD_TRANSPORT_FAMILY, - ) + transport_family=_CLOUD_TRANSPORT_FAMILY) if scope is None: return _err(rid, _ERR_FORBIDDEN, missing_scope_message) # Defense in depth: the broker's exact-scope ops already reject foreign @@ -143,8 +139,7 @@ def _register_precheck(rid, params: dict): @_controller_method( "browser.controller.register", identity_message="browser.controller.register requires an authenticated non-internal identity", - lookup_scope=False, precheck=_register_precheck, -) + lookup_scope=False, precheck=_register_precheck) def _(rid, params: dict, transport, identity, session_id, broker, _scope, session) -> dict: """Attach this connection as the browser controller for one session. @@ -167,8 +162,7 @@ def _(rid, params: dict, transport, identity, session_id, broker, _scope, sessio scope = browser_control_broker.ControllerScope( principal_id=_principal_digest(identity), profile_id=profile_id, session_id=session_id, controller_id=controller_id, browser_profile_id=browser_profile_id, - transport_family=_CLOUD_TRANSPORT_FAMILY, capabilities=capabilities, - ) + transport_family=_CLOUD_TRANSPORT_FAMILY, capabilities=capabilities) broker.attach(scope, _broker_event_writer(transport, session_id), owner=transport) return _ok(rid, { "scope": { @@ -178,9 +172,7 @@ def _(rid, params: dict, transport, identity, session_id, broker, _scope, sessio "controller_id": scope.controller_id, "browser_profile_id": scope.browser_profile_id, "transport_family": scope.transport_family, - "capabilities": sorted(scope.capabilities), - } - }) + "capabilities": sorted(scope.capabilities)}}) @_controller_method("browser.controller.result") @@ -195,8 +187,7 @@ def _(rid, params: dict, _transport, _identity, _session_id, broker, scope, _ses return _err(rid, _ERR_FORBIDDEN, "command_id required") ok = params.get("ok") is True accepted = broker.complete( - command_id, scope=scope, ok=ok, result=params.get("result") if ok else params.get("error") - ) + command_id, scope=scope, ok=ok, result=params.get("result") if ok else params.get("error")) return _ok(rid, {"accepted": accepted}) diff --git a/tui_gateway/model_switch.py b/tui_gateway/model_switch.py index 8df0d1ad55..b4ef15f410 100644 --- a/tui_gateway/model_switch.py +++ b/tui_gateway/model_switch.py @@ -50,8 +50,7 @@ def _restore_agent_model_runtime(agent, snapshot: dict | None) -> None: agent.switch_model( new_model=snapshot.get("model", ""), new_provider=snapshot.get("provider", ""), api_key=snapshot.get("api_key", ""), base_url=snapshot.get("base_url", ""), - api_mode=snapshot.get("api_mode", ""), capabilities=snapshot.get("capabilities"), - ) + api_mode=snapshot.get("api_mode", ""), capabilities=snapshot.get("capabilities")) @contextlib.contextmanager @@ -84,8 +83,7 @@ def _restart_completed_failed_agent_build(sid: str, session: dict, failed_ready: with build_lock: if ( session.get("agent") is not None or session.get("agent_error") is None - or session.get("agent_ready") is not failed_ready or not failed_ready.is_set() - ): + or session.get("agent_ready") is not failed_ready or not failed_ready.is_set()): return False model_override = session.get("model_override") resume_overrides = session.get("resume_runtime_overrides") @@ -108,8 +106,7 @@ def _restart_completed_failed_agent_build(sid: str, session: dict, failed_ready: def _switch_request(raw_input: str, parsed_flags, persist_override) -> tuple[str, str, bool, bool]: """Normalize /model flags → (model_input, explicit_provider, one_turn, persist_global); raises on conflict.""" from hermes_cli.model_switch import ( - MODEL_SWITCH_ERR_ONCE_WITH_GLOBAL, MODEL_SWITCH_ERROR_TEXT, parse_model_switch_args, - resolve_persist_behavior, + MODEL_SWITCH_ERR_ONCE_WITH_GLOBAL, MODEL_SWITCH_ERROR_TEXT, parse_model_switch_args, resolve_persist_behavior ) if parsed_flags is None: @@ -126,8 +123,7 @@ def _switch_request(raw_input: str, parsed_flags, persist_override) -> tuple[str raise ValueError(MODEL_SWITCH_ERROR_TEXT[MODEL_SWITCH_ERR_ONCE_WITH_GLOBAL]) if persist_override is None: persist_override = resolve_persist_behavior( - is_global_flag, is_session, is_once=one_turn, explicit_provider=explicit_provider - ) + is_global_flag, is_session, is_once=one_turn, explicit_provider=explicit_provider) if not model_input: raise ValueError("model value required") return model_input, explicit_provider, one_turn, persist_override @@ -178,8 +174,7 @@ def _merge_preflight_warning(result, agent, session: dict, cfg, custom_provs) -> cfg_ctx = int(mc["context_length"]) merge_preflight_compression_warning( result, agent=agent, messages=list(session.get("history", [])), - custom_providers=custom_provs, config_context_length=cfg_ctx, - ) + custom_providers=custom_provs, config_context_length=cfg_ctx) except Exception as exc: logger.debug("preflight-compression switch warning failed: %s", exc) @@ -191,8 +186,7 @@ def _expensive_model_confirm(result, current_base_url: str, current_api_key) -> warning = combined_selection_warning( result.new_model, provider=result.target_provider, base_url=result.base_url or current_base_url, - api_key=result.api_key or current_api_key, model_info=result.model_info, - ) + api_key=result.api_key or current_api_key, model_info=result.model_info) except Exception: warning = None if warning is None: @@ -211,8 +205,7 @@ def _commit_agent_switch(sid: str, session: dict, agent, result, current_model: agent.switch_model( new_model=result.new_model, new_provider=result.target_provider, api_key=result.api_key, base_url=result.base_url, api_mode=result.api_mode, - capabilities=getattr(result, "runtime_capabilities", None), - ) + capabilities=getattr(result, "runtime_capabilities", None)) except Exception as exc: # The in-place swap rolled the agent back and re-raised. Abort the whole # commit (worker restart, persist, marker, override, config write) or the @@ -236,19 +229,16 @@ def _commit_agent_switch(sid: str, session: dict, agent, result, current_model: def _apply_model_switch( sid: str, session: dict, raw_input: str, *, confirm_expensive_model: bool = False, pin_session_override: bool = True, parsed_flags: Any | None = None, - persist_override: bool | None = None, -) -> dict: + persist_override: bool | None = None) -> dict: from hermes_cli.model_switch import switch_model model_input, explicit_provider, one_turn, persist_global = _switch_request( - raw_input, parsed_flags, persist_override - ) + raw_input, parsed_flags, persist_override) agent = session.get("agent") if one_turn and not agent: raise ValueError("/model --once requires a live session") current_provider, current_model, current_base_url, current_api_key = _current_model_runtime( - agent, explicit_provider - ) + agent, explicit_provider) # User-defined providers let switch_model resolve named custom endpoints # (e.g. "ollama-launch") and validate against saved model lists. user_provs, custom_provs, cfg = _provider_context() @@ -275,16 +265,14 @@ def _apply_model_switch( if pin_session_override and isinstance(session, dict) and not one_turn: session["model_override"] = { "model": result.new_model, "provider": result.target_provider, - "base_url": result.base_url, "api_key": result.api_key, "api_mode": result.api_mode, - } + "base_url": result.base_url, "api_key": result.api_key, "api_mode": result.api_mode} if persist_global: _persist_model_switch(result) return { "value": result.new_model, "warning": result.warning_message or "", "confirm_required": False, - "scope": "once" if one_turn else ("global" if persist_global else "session"), - } + "scope": "once" if one_turn else ("global" if persist_global else "session")} def _sync_bot_capabilities(sid: str, session: dict) -> None: