From 20a921e9dcf13c667a853269d947dd3668144dd5 Mon Sep 17 00:00:00 2001 From: Oleg Klimov Date: Fri, 6 Feb 2026 14:02:04 +0100 Subject: [PATCH 1/5] flexus_persona_authorized --- flexus_client_kit/ckit_bot_exec.py | 8 +++++++- flexus_client_kit/ckit_bot_query.py | 12 ++++++++++++ flexus_simple_bots/frog/frog_install.py | 2 ++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/flexus_client_kit/ckit_bot_exec.py b/flexus_client_kit/ckit_bot_exec.py index e22cfcc7..7aa85969 100644 --- a/flexus_client_kit/ckit_bot_exec.py +++ b/flexus_client_kit/ckit_bot_exec.py @@ -408,7 +408,11 @@ async def subscribe_and_produce_callbacks( reassign_threads = False # logger.info("subs %s %s %s" % (upd.news_action, upd.news_about, upd.news_payload_id)) - if upd.news_about == "flexus_persona": + if upd.news_about == "flexus_external_auth": + if upd.news_action in ["INSERT", "UPDATE"]: + bc.auth[upd.news_payload_auth.auth_persona_id][upd.news_payload_auth.auth_service_provider] = upd.news_payload_auth.auth_key2value + + elif upd.news_about == "flexus_persona": if upd.news_action in ["INSERT", "UPDATE"]: assert upd.news_payload_persona.ws_id assert upd.news_payload_persona.ws_timezone @@ -538,6 +542,8 @@ async def subscribe_and_produce_callbacks( if bot := bc.bots_running.get(emsg.emsg_persona_id): bot.instance_rcx._parked_emessages[emsg.emsg_id] = emsg bot.instance_rcx._parked_anything_new.set() + else: + logger.warning("External message about persona %s, but no bot is running it." % emsg.emsg_persona_id) elif upd.news_action == "INITIAL_UPDATES_OVER": if len(bc.bots_running) == 0: diff --git a/flexus_client_kit/ckit_bot_query.py b/flexus_client_kit/ckit_bot_query.py index 8803c6ca..7efcd288 100644 --- a/flexus_client_kit/ckit_bot_query.py +++ b/flexus_client_kit/ckit_bot_query.py @@ -49,6 +49,17 @@ class FExternalMessageOutput: ws_id: str +@dataclass +class FExternalAuth: + auth_id: str + auth_persona_id: str + auth_name: str + auth_auth_type: str + auth_service_provider: str + auth_key2value: Any # {"TOKEN_ID": "TOKEN_VAL", ...} + ws_id: str + + @dataclass class FBotThreadsCallsTasks: news_action: str @@ -62,6 +73,7 @@ class FBotThreadsCallsTasks: news_payload_erp_record_new: Optional[Dict[str, Any]] = None news_payload_erp_record_old: Optional[Dict[str, Any]] = None news_payload_emessage: Optional[FExternalMessageOutput] = None + news_payload_auth: Optional[FExternalAuth] = None @dataclass diff --git a/flexus_simple_bots/frog/frog_install.py b/flexus_simple_bots/frog/frog_install.py index c830bad7..64845f19 100644 --- a/flexus_simple_bots/frog/frog_install.py +++ b/flexus_simple_bots/frog/frog_install.py @@ -104,6 +104,8 @@ async def install( marketable_github_repo="https://github.com/smallcloudai/flexus-client-kit.git", marketable_run_this="python -m flexus_simple_bots.frog.frog_bot", marketable_setup_default=frog_setup_schema, + marketable_auth_needed=[], + marketable_auth_supported=[], marketable_featured_actions=[ {"feat_question": "Ribbit! Tell me something fun", "feat_expert": "default", "feat_depends_on_setup": []}, {"feat_question": "Give me a motivational boost", "feat_expert": "default", "feat_depends_on_setup": []}, From 7d45a98e1987bcba83992ec214b659bd3835fa63 Mon Sep 17 00:00:00 2001 From: Valerii Kliuchnikov <56191756+kliuchnikovv@users.noreply.github.com> Date: Tue, 10 Feb 2026 14:52:29 +0100 Subject: [PATCH 2/5] Add optional deps to frog as example --- flexus_client_kit/ckit_bot_exec.py | 108 +++++++++++++++++++++--- flexus_client_kit/ckit_bot_install.py | 10 ++- flexus_client_kit/ckit_bot_query.py | 5 +- flexus_simple_bots/frog/frog_bot.py | 9 +- flexus_simple_bots/frog/frog_install.py | 2 +- 5 files changed, 117 insertions(+), 17 deletions(-) diff --git a/flexus_client_kit/ckit_bot_exec.py b/flexus_client_kit/ckit_bot_exec.py index 7aa85969..69e049f3 100644 --- a/flexus_client_kit/ckit_bot_exec.py +++ b/flexus_client_kit/ckit_bot_exec.py @@ -66,7 +66,7 @@ def official_setup_mixing_procedure(marketable_setup_default, persona_setup) -> class RobotContext: - def __init__(self, fclient: ckit_client.FlexusClient, p: ckit_bot_query.FPersonaOutput): + def __init__(self, fclient: ckit_client.FlexusClient, p: ckit_bot_query.FPersonaOutput, external_auth: Optional[Dict[str, Any]] = None): self._handler_updated_message: Optional[Callable[[ckit_ask_model.FThreadMessageOutput], Awaitable[None]]] = None self._handler_upd_thread: Optional[Callable[[ckit_ask_model.FThreadOutput], Awaitable[None]]] = None self._handler_updated_task: Optional[Callable[[ckit_kanban.FPersonaKanbanTaskOutput], Awaitable[None]]] = None @@ -92,6 +92,7 @@ def __init__(self, fclient: ckit_client.FlexusClient, p: ckit_bot_query.FPersona self.workdir = "/tmp/bot_workspace/%s/" % p.persona_id self.running_test_scenario = False self.running_happy_yaml = "" + self.external_auth = external_auth or {} os.makedirs(self.workdir, exist_ok=True) def on_updated_message(self, handler: Callable[[ckit_ask_model.FThreadMessageOutput], Awaitable[None]]): @@ -366,6 +367,8 @@ def __init__( self.running_happy_yaml = running_happy_yaml self.subscribe_to_erp_tables = subscribe_to_erp_tables self.subscribe_to_emsg_types = subscribe_to_emsg_types + self.auth: Dict[str, Dict[str, Any]] = {} + self.personas: Dict[str, ckit_bot_query.FPersonaOutput] = {} async def subscribe_and_produce_callbacks( @@ -382,6 +385,54 @@ async def subscribe_and_produce_callbacks( if bc.subscribe_to_erp_tables: logger.info(f"Subscribing to ERP tables: {bc.subscribe_to_erp_tables}") + def start_bot(persona_id: str) -> bool: + if persona_id in bc.bots_running: + return False # Already running + + persona = bc.personas.get(persona_id) + if not persona: + return False # No persona data yet + + # Merge persona-level and workspace-level auth (workspace overrides persona) + ws_id = persona.ws_id + persona_auth = bc.auth.get(persona_id, {}) + workspace_auth = bc.auth.get(ws_id, {}) + merged_auth = {**workspace_auth, **persona_auth} # persona takes precedence + + auth_needed = persona.marketable_auth_needed or [] + missing_auth = [p for p in auth_needed if p not in merged_auth] + + if missing_auth: + logger.warning("Persona %s NOT starting: missing required auth providers: %s" % (persona_id, missing_auth)) + logger.info(" Available auth: %s" % (list(merged_auth.keys()))) + logger.info(" Workspace auth (%s): %s" % (ws_id, list(workspace_auth.keys()))) + logger.info(" Persona auth (%s): %s" % (persona_id, list(persona_auth.keys()))) + return False + + logger.info("Persona %s starting with auth: %s" % (persona_id, list(merged_auth.keys()))) + for provider, content in merged_auth.items(): + logger.info(" Auth[%s] keys: %s" % (provider, list(content.keys()) if isinstance(content, dict) else type(content))) + rcx = RobotContext(fclient, persona, merged_auth) + rcx.running_test_scenario = bc.running_test_scenario + rcx.running_happy_yaml = bc.running_happy_yaml + bc.bots_running[persona_id] = BotInstance( + fclient=fclient, + atask=asyncio.create_task(crash_boom_bang(fclient, rcx, bc.bot_main_loop)), + instance_rcx=rcx, + ) + return True + + def restart_bot(persona_id: str, reason: str) -> bool: + if bot := bc.bots_running.get(persona_id, None): + logger.info(reason) + del bc.bots_running[persona_id] + bc.shutting_down_tasks.add(bot.atask) + bot.atask.add_done_callback(bc.shutting_down_tasks.discard) + bot.instance_rcx._restart_requested = True + bot.instance_rcx._parked_anything_new.set() + return True + return False + async with ws_client as ws: assert fclient.ws_id is not None or fclient.group_id is not None # group_id takes priority over ws_id, send only one (not both) @@ -409,8 +460,39 @@ async def subscribe_and_produce_callbacks( # logger.info("subs %s %s %s" % (upd.news_action, upd.news_about, upd.news_payload_id)) if upd.news_about == "flexus_external_auth": + handled = True if upd.news_action in ["INSERT", "UPDATE"]: - bc.auth[upd.news_payload_auth.auth_persona_id][upd.news_payload_auth.auth_service_provider] = upd.news_payload_auth.auth_key2value + if upd.news_payload_auth is None: + continue + + persona_id = upd.news_payload_auth.auth_persona_id + ws_id = upd.news_payload_auth.ws_id + provider = upd.news_payload_auth.auth_service_provider + is_workspace_token = not persona_id or persona_id == "" + + logger.info(f"Received auth: persona_id={persona_id}, ws_id={ws_id}, provider={provider}, keys={list(upd.news_payload_auth.auth_key2value.keys())}") + + # Store auth tokens (use persona_id for persona-scoped, ws_id for workspace-scoped) + auth_key = persona_id if not is_workspace_token else ws_id + if auth_key not in bc.auth: + bc.auth[auth_key] = {} + bc.auth[auth_key][provider] = upd.news_payload_auth.auth_key2value + logger.info(f"Stored in bc.auth[{auth_key}][{provider}] with {len(upd.news_payload_auth.auth_key2value)} keys") + + elif upd.news_action == "DELETE": + if upd.news_payload_auth is None: + continue + + persona_id = upd.news_payload_auth.auth_persona_id + ws_id = upd.news_payload_auth.ws_id + provider = upd.news_payload_auth.auth_service_provider + + # Remove auth tokens + auth_key = persona_id if persona_id else ws_id + if auth_key in bc.auth: + bc.auth[auth_key].pop(provider, None) + logger.info(f"Removed auth {provider} from bc.auth[{auth_key}]") + elif upd.news_about == "flexus_persona": if upd.news_action in ["INSERT", "UPDATE"]: @@ -419,6 +501,8 @@ async def subscribe_and_produce_callbacks( handled = True persona_id = upd.news_payload_id + bc.personas[persona_id] = upd.news_payload_persona + if bot := bc.bots_running.get(persona_id, None): if bot.instance_rcx.persona.persona_setup != upd.news_payload_persona.persona_setup: logger.info("Persona %s setup changed, requesting graceful shutdown" % persona_id) @@ -428,15 +512,17 @@ async def subscribe_and_produce_callbacks( bot.instance_rcx._restart_requested = True bot.instance_rcx._parked_anything_new.set() if persona_id not in bc.bots_running: - rcx = RobotContext(fclient, upd.news_payload_persona) - rcx.running_test_scenario = bc.running_test_scenario - rcx.running_happy_yaml = bc.running_happy_yaml - bc.bots_running[persona_id] = BotInstance( - fclient=fclient, - atask=asyncio.create_task(crash_boom_bang(fclient, rcx, bc.bot_main_loop)), - instance_rcx=rcx, - ) + if start_bot(persona_id): + reassign_threads = True + + elif upd.news_action == "RESTART": + handled = True + persona_id = upd.news_payload_id + logger.info("Received RESTART request for persona %s" % persona_id) + if restart_bot(persona_id, f"Explicit restart requested for {persona_id}"): reassign_threads = True + if start_bot(persona_id): + reassign_threads = True elif upd.news_action == "DELETE": handled = True @@ -448,6 +534,8 @@ async def subscribe_and_produce_callbacks( except asyncio.CancelledError: pass del bc.bots_running[persona_id] + if persona_id in bc.personas: + del bc.personas[persona_id] elif upd.news_about == "flexus_thread": if upd.news_action in ["INSERT", "UPDATE"]: diff --git a/flexus_client_kit/ckit_bot_install.py b/flexus_client_kit/ckit_bot_install.py index 3901c198..f885126d 100644 --- a/flexus_client_kit/ckit_bot_install.py +++ b/flexus_client_kit/ckit_bot_install.py @@ -78,6 +78,8 @@ async def marketplace_upsert_dev_bot( marketable_tags: List[str] = [], marketable_forms: Optional[Dict[str, str]] = None, marketable_required_policydocs: List[str] = [], + marketable_auth_needed: List[str] = [], + marketable_auth_supported: List[str] = [], ) -> FBotInstallOutput: assert not ws_id.startswith("fx-"), "You can find workspace id in the browser address bar, when visiting for example the statistics page" http = await client.use_http() @@ -89,7 +91,7 @@ async def marketplace_upsert_dev_bot( experts_input.append(expert_dict) # NOTE: marketable_stage removed from mutation for staging API compatibility r = await h.execute( - gql.gql(f"""mutation InstallBot($ws: String!, $name: String!, $ver: String!, $title1: String!, $title2: String!, $author: String!, $accent_color: String!, $occupation: String!, $desc: String!, $typical_group: String!, $repo: String!, $run: String!, $setup: String!, $featured: [FFeaturedActionInput!]!, $intro: String!, $model: String!, $daily: Int!, $inbox: Int!, $experts: [FMarketplaceExpertInput!]!, $schedule: String!, $big: String!, $small: String!, $tags: [String!]!, $forms: String, $required_policydocs: [String!]!) {{ + gql.gql(f"""mutation InstallBot($ws: String!, $name: String!, $ver: String!, $title1: String!, $title2: String!, $author: String!, $accent_color: String!, $occupation: String!, $desc: String!, $typical_group: String!, $repo: String!, $run: String!, $setup: String!, $featured: [FFeaturedActionInput!]!, $intro: String!, $model: String!, $daily: Int!, $inbox: Int!, $experts: [FMarketplaceExpertInput!]!, $schedule: String!, $big: String!, $small: String!, $tags: [String!]!, $forms: String, $required_policydocs: [String!]!, $auth_needed: [String!]!, $auth_supported: [String!]!) {{ marketplace_upsert_dev_bot( ws_id: $ws, marketable_name: $name, @@ -115,7 +117,9 @@ async def marketplace_upsert_dev_bot( marketable_picture_small_b64: $small, marketable_tags: $tags, marketable_forms: $forms, - marketable_required_policydocs: $required_policydocs + marketable_required_policydocs: $required_policydocs, + marketable_auth_needed: $auth_needed, + marketable_auth_supported: $auth_supported ) {{ {gql_utils.gql_fields(FBotInstallOutput)} }} @@ -146,6 +150,8 @@ async def marketplace_upsert_dev_bot( "small": marketable_picture_small_b64, "forms": json.dumps(marketable_forms or {}), "required_policydocs": marketable_required_policydocs, + "auth_needed": marketable_auth_needed, + "auth_supported": marketable_auth_supported, }, ) return gql_utils.dataclass_from_dict(r["marketplace_upsert_dev_bot"], FBotInstallOutput) diff --git a/flexus_client_kit/ckit_bot_query.py b/flexus_client_kit/ckit_bot_query.py index 7efcd288..551df01c 100644 --- a/flexus_client_kit/ckit_bot_query.py +++ b/flexus_client_kit/ckit_bot_query.py @@ -25,6 +25,8 @@ class FPersonaOutput: marketable_run_this: Optional[str] = None marketable_stage: Optional[str] = None marketable_radix: Optional[int] = None + marketable_auth_needed: Optional[List[str]] = None + marketable_auth_supported: Optional[List[str]] = None @dataclass @@ -48,7 +50,6 @@ class FExternalMessageOutput: emsg_created_ts: float ws_id: str - @dataclass class FExternalAuth: auth_id: str @@ -59,7 +60,6 @@ class FExternalAuth: auth_key2value: Any # {"TOKEN_ID": "TOKEN_VAL", ...} ws_id: str - @dataclass class FBotThreadsCallsTasks: news_action: str @@ -75,7 +75,6 @@ class FBotThreadsCallsTasks: news_payload_emessage: Optional[FExternalMessageOutput] = None news_payload_auth: Optional[FExternalAuth] = None - @dataclass class FThreadWithMessages: persona_id: str diff --git a/flexus_simple_bots/frog/frog_bot.py b/flexus_simple_bots/frog/frog_bot.py index fcaf2902..9e3efa8f 100644 --- a/flexus_simple_bots/frog/frog_bot.py +++ b/flexus_simple_bots/frog/frog_bot.py @@ -95,9 +95,16 @@ ] -async def frog_main_loop(fclient: ckit_client.FlexusClient, rcx: ckit_bot_exec.RobotContext) -> None: +async def frog_main_loop(client: ckit_client.FlexusClient, rcx: ckit_bot_exec.RobotContext) -> None: setup = ckit_bot_exec.official_setup_mixing_procedure(frog_install.frog_setup_schema, rcx.persona.persona_setup) + # Example of getting token from external_auth + elevenlabs_auth = rcx.external_auth.get("elevenlabs", {}) + elevenlabs_token = elevenlabs_auth.get("token") if elevenlabs_auth else None + if elevenlabs_token: + masked = elevenlabs_token[:8] + "..." + elevenlabs_token[-4:] if len(elevenlabs_token) > 12 else "***" + logger.info("Frog has ELEVENLABS token: %s (len=%d)", masked, len(elevenlabs_token)) + mongo_conn_str = await ckit_mongo.mongo_fetch_creds(fclient, rcx.persona.persona_id) mongo = AsyncMongoClient(mongo_conn_str) dbname = rcx.persona.persona_id + "_db" diff --git a/flexus_simple_bots/frog/frog_install.py b/flexus_simple_bots/frog/frog_install.py index 64845f19..f23a888a 100644 --- a/flexus_simple_bots/frog/frog_install.py +++ b/flexus_simple_bots/frog/frog_install.py @@ -105,7 +105,7 @@ async def install( marketable_run_this="python -m flexus_simple_bots.frog.frog_bot", marketable_setup_default=frog_setup_schema, marketable_auth_needed=[], - marketable_auth_supported=[], + marketable_auth_supported=["elevenlabs"], marketable_featured_actions=[ {"feat_question": "Ribbit! Tell me something fun", "feat_expert": "default", "feat_depends_on_setup": []}, {"feat_question": "Give me a motivational boost", "feat_expert": "default", "feat_depends_on_setup": []}, From 3a89452bb9c67de279affdb2784118d31d924aa7 Mon Sep 17 00:00:00 2001 From: Valerii Kliuchnikov <56191756+kliuchnikovv@users.noreply.github.com> Date: Wed, 11 Feb 2026 11:58:16 +0100 Subject: [PATCH 3/5] fixes --- flexus_client_kit/ckit_bot_exec.py | 5 +++++ flexus_simple_bots/frog/frog_bot.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/flexus_client_kit/ckit_bot_exec.py b/flexus_client_kit/ckit_bot_exec.py index 69e049f3..e8d841a5 100644 --- a/flexus_client_kit/ckit_bot_exec.py +++ b/flexus_client_kit/ckit_bot_exec.py @@ -400,6 +400,8 @@ def start_bot(persona_id: str) -> bool: merged_auth = {**workspace_auth, **persona_auth} # persona takes precedence auth_needed = persona.marketable_auth_needed or [] + auth_supported = persona.marketable_auth_supported or [] + auth_allowed = set(auth_needed) | set(auth_supported) missing_auth = [p for p in auth_needed if p not in merged_auth] if missing_auth: @@ -409,6 +411,9 @@ def start_bot(persona_id: str) -> bool: logger.info(" Persona auth (%s): %s" % (persona_id, list(persona_auth.keys()))) return False + # Only pass auth providers that the bot declared in auth_needed or auth_supported + merged_auth = {k: v for k, v in merged_auth.items() if k in auth_allowed} + logger.info("Persona %s starting with auth: %s" % (persona_id, list(merged_auth.keys()))) for provider, content in merged_auth.items(): logger.info(" Auth[%s] keys: %s" % (provider, list(content.keys()) if isinstance(content, dict) else type(content))) diff --git a/flexus_simple_bots/frog/frog_bot.py b/flexus_simple_bots/frog/frog_bot.py index 9e3efa8f..c7485e9c 100644 --- a/flexus_simple_bots/frog/frog_bot.py +++ b/flexus_simple_bots/frog/frog_bot.py @@ -105,7 +105,7 @@ async def frog_main_loop(client: ckit_client.FlexusClient, rcx: ckit_bot_exec.Ro masked = elevenlabs_token[:8] + "..." + elevenlabs_token[-4:] if len(elevenlabs_token) > 12 else "***" logger.info("Frog has ELEVENLABS token: %s (len=%d)", masked, len(elevenlabs_token)) - mongo_conn_str = await ckit_mongo.mongo_fetch_creds(fclient, rcx.persona.persona_id) + mongo_conn_str = await ckit_mongo.mongo_fetch_creds(client, rcx.persona.persona_id) mongo = AsyncMongoClient(mongo_conn_str) dbname = rcx.persona.persona_id + "_db" mydb = mongo[dbname] From 9b31d05f4fb6dc9ec95a806c4cf0db0617ca5048 Mon Sep 17 00:00:00 2001 From: Valerii Kliuchnikov <56191756+kliuchnikovv@users.noreply.github.com> Date: Thu, 12 Feb 2026 11:28:49 +0100 Subject: [PATCH 4/5] Cleanup --- flexus_client_kit/ckit_bot_exec.py | 85 +++-------------------------- flexus_simple_bots/frog/frog_bot.py | 4 +- 2 files changed, 10 insertions(+), 79 deletions(-) diff --git a/flexus_client_kit/ckit_bot_exec.py b/flexus_client_kit/ckit_bot_exec.py index e8d841a5..c227fa74 100644 --- a/flexus_client_kit/ckit_bot_exec.py +++ b/flexus_client_kit/ckit_bot_exec.py @@ -368,7 +368,6 @@ def __init__( self.subscribe_to_erp_tables = subscribe_to_erp_tables self.subscribe_to_emsg_types = subscribe_to_emsg_types self.auth: Dict[str, Dict[str, Any]] = {} - self.personas: Dict[str, ckit_bot_query.FPersonaOutput] = {} async def subscribe_and_produce_callbacks( @@ -385,59 +384,6 @@ async def subscribe_and_produce_callbacks( if bc.subscribe_to_erp_tables: logger.info(f"Subscribing to ERP tables: {bc.subscribe_to_erp_tables}") - def start_bot(persona_id: str) -> bool: - if persona_id in bc.bots_running: - return False # Already running - - persona = bc.personas.get(persona_id) - if not persona: - return False # No persona data yet - - # Merge persona-level and workspace-level auth (workspace overrides persona) - ws_id = persona.ws_id - persona_auth = bc.auth.get(persona_id, {}) - workspace_auth = bc.auth.get(ws_id, {}) - merged_auth = {**workspace_auth, **persona_auth} # persona takes precedence - - auth_needed = persona.marketable_auth_needed or [] - auth_supported = persona.marketable_auth_supported or [] - auth_allowed = set(auth_needed) | set(auth_supported) - missing_auth = [p for p in auth_needed if p not in merged_auth] - - if missing_auth: - logger.warning("Persona %s NOT starting: missing required auth providers: %s" % (persona_id, missing_auth)) - logger.info(" Available auth: %s" % (list(merged_auth.keys()))) - logger.info(" Workspace auth (%s): %s" % (ws_id, list(workspace_auth.keys()))) - logger.info(" Persona auth (%s): %s" % (persona_id, list(persona_auth.keys()))) - return False - - # Only pass auth providers that the bot declared in auth_needed or auth_supported - merged_auth = {k: v for k, v in merged_auth.items() if k in auth_allowed} - - logger.info("Persona %s starting with auth: %s" % (persona_id, list(merged_auth.keys()))) - for provider, content in merged_auth.items(): - logger.info(" Auth[%s] keys: %s" % (provider, list(content.keys()) if isinstance(content, dict) else type(content))) - rcx = RobotContext(fclient, persona, merged_auth) - rcx.running_test_scenario = bc.running_test_scenario - rcx.running_happy_yaml = bc.running_happy_yaml - bc.bots_running[persona_id] = BotInstance( - fclient=fclient, - atask=asyncio.create_task(crash_boom_bang(fclient, rcx, bc.bot_main_loop)), - instance_rcx=rcx, - ) - return True - - def restart_bot(persona_id: str, reason: str) -> bool: - if bot := bc.bots_running.get(persona_id, None): - logger.info(reason) - del bc.bots_running[persona_id] - bc.shutting_down_tasks.add(bot.atask) - bot.atask.add_done_callback(bc.shutting_down_tasks.discard) - bot.instance_rcx._restart_requested = True - bot.instance_rcx._parked_anything_new.set() - return True - return False - async with ws_client as ws: assert fclient.ws_id is not None or fclient.group_id is not None # group_id takes priority over ws_id, send only one (not both) @@ -506,28 +452,15 @@ def restart_bot(persona_id: str, reason: str) -> bool: handled = True persona_id = upd.news_payload_id - bc.personas[persona_id] = upd.news_payload_persona - - if bot := bc.bots_running.get(persona_id, None): - if bot.instance_rcx.persona.persona_setup != upd.news_payload_persona.persona_setup: - logger.info("Persona %s setup changed, requesting graceful shutdown" % persona_id) - del bc.bots_running[persona_id] - bc.shutting_down_tasks.add(bot.atask) - bot.atask.add_done_callback(bc.shutting_down_tasks.discard) - bot.instance_rcx._restart_requested = True - bot.instance_rcx._parked_anything_new.set() if persona_id not in bc.bots_running: - if start_bot(persona_id): - reassign_threads = True - - elif upd.news_action == "RESTART": - handled = True - persona_id = upd.news_payload_id - logger.info("Received RESTART request for persona %s" % persona_id) - if restart_bot(persona_id, f"Explicit restart requested for {persona_id}"): - reassign_threads = True - if start_bot(persona_id): - reassign_threads = True + rcx = RobotContext(fclient, upd.news_payload_persona, bc.auth.get(persona_id, {})) + rcx.running_test_scenario = bc.running_test_scenario + rcx.running_happy_yaml = bc.running_happy_yaml + bc.bots_running[persona_id] = BotInstance( + fclient=fclient, + atask=asyncio.create_task(crash_boom_bang(fclient, rcx, bc.bot_main_loop)), + instance_rcx=rcx, + ) elif upd.news_action == "DELETE": handled = True @@ -539,8 +472,6 @@ def restart_bot(persona_id: str, reason: str) -> bool: except asyncio.CancelledError: pass del bc.bots_running[persona_id] - if persona_id in bc.personas: - del bc.personas[persona_id] elif upd.news_about == "flexus_thread": if upd.news_action in ["INSERT", "UPDATE"]: diff --git a/flexus_simple_bots/frog/frog_bot.py b/flexus_simple_bots/frog/frog_bot.py index c7485e9c..48770f1c 100644 --- a/flexus_simple_bots/frog/frog_bot.py +++ b/flexus_simple_bots/frog/frog_bot.py @@ -95,7 +95,7 @@ ] -async def frog_main_loop(client: ckit_client.FlexusClient, rcx: ckit_bot_exec.RobotContext) -> None: +async def frog_main_loop(fclient: ckit_client.FlexusClient, rcx: ckit_bot_exec.RobotContext) -> None: setup = ckit_bot_exec.official_setup_mixing_procedure(frog_install.frog_setup_schema, rcx.persona.persona_setup) # Example of getting token from external_auth @@ -105,7 +105,7 @@ async def frog_main_loop(client: ckit_client.FlexusClient, rcx: ckit_bot_exec.Ro masked = elevenlabs_token[:8] + "..." + elevenlabs_token[-4:] if len(elevenlabs_token) > 12 else "***" logger.info("Frog has ELEVENLABS token: %s (len=%d)", masked, len(elevenlabs_token)) - mongo_conn_str = await ckit_mongo.mongo_fetch_creds(client, rcx.persona.persona_id) + mongo_conn_str = await ckit_mongo.mongo_fetch_creds(fclient, rcx.persona.persona_id) mongo = AsyncMongoClient(mongo_conn_str) dbname = rcx.persona.persona_id + "_db" mydb = mongo[dbname] From c0e11c8420cb2ee2b31ebea54dacb25a90cf2939 Mon Sep 17 00:00:00 2001 From: Valerii Kliuchnikov <56191756+kliuchnikovv@users.noreply.github.com> Date: Thu, 12 Feb 2026 16:55:29 +0100 Subject: [PATCH 5/5] Fix missing auth_name --- flexus_client_kit/ckit_bot_exec.py | 2 ++ flexus_client_kit/ckit_bot_query.py | 1 - flexus_client_kit/ckit_external_auth.py | 4 ---- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/flexus_client_kit/ckit_bot_exec.py b/flexus_client_kit/ckit_bot_exec.py index c227fa74..68e77272 100644 --- a/flexus_client_kit/ckit_bot_exec.py +++ b/flexus_client_kit/ckit_bot_exec.py @@ -429,6 +429,7 @@ async def subscribe_and_produce_callbacks( bc.auth[auth_key] = {} bc.auth[auth_key][provider] = upd.news_payload_auth.auth_key2value logger.info(f"Stored in bc.auth[{auth_key}][{provider}] with {len(upd.news_payload_auth.auth_key2value)} keys") + bc.bots_running[persona_id].instance_rcx._restart_requested = True elif upd.news_action == "DELETE": if upd.news_payload_auth is None: @@ -443,6 +444,7 @@ async def subscribe_and_produce_callbacks( if auth_key in bc.auth: bc.auth[auth_key].pop(provider, None) logger.info(f"Removed auth {provider} from bc.auth[{auth_key}]") + bc.bots_running[persona_id].instance_rcx._restart_requested = True elif upd.news_about == "flexus_persona": diff --git a/flexus_client_kit/ckit_bot_query.py b/flexus_client_kit/ckit_bot_query.py index 551df01c..2e1f695f 100644 --- a/flexus_client_kit/ckit_bot_query.py +++ b/flexus_client_kit/ckit_bot_query.py @@ -54,7 +54,6 @@ class FExternalMessageOutput: class FExternalAuth: auth_id: str auth_persona_id: str - auth_name: str auth_auth_type: str auth_service_provider: str auth_key2value: Any # {"TOKEN_ID": "TOKEN_VAL", ...} diff --git a/flexus_client_kit/ckit_external_auth.py b/flexus_client_kit/ckit_external_auth.py index 53451d81..9ac901f5 100644 --- a/flexus_client_kit/ckit_external_auth.py +++ b/flexus_client_kit/ckit_external_auth.py @@ -32,7 +32,6 @@ async def upsert_external_auth( fclient: ckit_client.FlexusClient, persona_id: str, auth_searchable: str, - auth_name: str, auth_service_provider: str, auth_json: dict, ) -> None: @@ -43,14 +42,12 @@ async def upsert_external_auth( mutation UpsertExternalAuth( $persona_id: String!, $auth_searchable: String!, - $auth_name: String!, $auth_service_provider: String!, $auth_json: String! ) {{ upsert_external_auth( persona_id: $persona_id, auth_searchable: $auth_searchable, - auth_name: $auth_name, auth_service_provider: $auth_service_provider, auth_json: $auth_json ) @@ -58,7 +55,6 @@ async def upsert_external_auth( variable_values={ "persona_id": persona_id, "auth_searchable": auth_searchable, - "auth_name": auth_name, "auth_service_provider": auth_service_provider, "auth_json": json.dumps(auth_json), },