From 1a70771b7a559fb04cefe55ce750cd6557281fd0 Mon Sep 17 00:00:00 2001 From: Frost Ming Date: Sun, 11 Oct 2026 15:15:32 +0800 Subject: [PATCH] chore: bump bub to 0.5.1 and keep existing config in onboarding bub 0.5.1 passes the loaded configuration to `onboard_config` and lets `bub onboard ` reconfigure a single plugin (bubbuild/bub#337). Prefill the dingtalk, discord and feishu prompts from the current config, and keep existing secrets for all channel plugins when the answer is left empty, so re-running onboarding no longer wipes values. Co-Authored-By: Claude Opus 5.5 --- .../bub-dingtalk/src/bub_dingtalk/plugin.py | 12 ++++-- .../tests/test_dingtalk_plugin.py | 25 ++++++++++++ .../bub-discord/src/bub_discord/plugin.py | 24 +++++++++--- .../bub-discord/tests/test_discord_plugin.py | 27 +++++++++++++ packages/bub-feishu/src/bub_feishu/plugin.py | 39 ++++++++++++++----- .../bub-feishu/tests/test_feishu_plugin.py | 30 ++++++++++++++ packages/bub-qq/src/bub_qq/plugin.py | 7 ++-- packages/bub-qq/tests/test_qq_plugin.py | 25 ++++++++++++ packages/bub-wecom/src/bub_wecom/plugin.py | 3 +- packages/bub-wecom/tests/test_wecom_plugin.py | 25 ++++++++++++ uv.lock | 4 +- 11 files changed, 197 insertions(+), 24 deletions(-) diff --git a/packages/bub-dingtalk/src/bub_dingtalk/plugin.py b/packages/bub-dingtalk/src/bub_dingtalk/plugin.py index 35cda82..28562c0 100644 --- a/packages/bub-dingtalk/src/bub_dingtalk/plugin.py +++ b/packages/bub-dingtalk/src/bub_dingtalk/plugin.py @@ -32,11 +32,17 @@ def onboard_config(current_config: dict[str, Any]) -> dict[str, Any] | None: if not _channel_enabled(current_config): return None - client_id = bub_inquirer.ask_text("DingTalk client ID") - client_secret = bub_inquirer.ask_secret("DingTalk client secret") + current = current_config.get(CHANNEL_NAME) + config = current if isinstance(current, dict) else {} + client_id = bub_inquirer.ask_text( + "DingTalk client ID", default=str(config.get("client_id") or "") + ) + client_secret = bub_inquirer.ask_secret("DingTalk client secret") or str( + config.get("client_secret") or "" + ) allow_users = bub_inquirer.ask_text( "DingTalk allowed users (comma-separated staff IDs, * for all)", - default="*", + default=str(config.get("allow_users") or "*"), ) return { diff --git a/packages/bub-dingtalk/tests/test_dingtalk_plugin.py b/packages/bub-dingtalk/tests/test_dingtalk_plugin.py index bacd583..c3664d6 100644 --- a/packages/bub-dingtalk/tests/test_dingtalk_plugin.py +++ b/packages/bub-dingtalk/tests/test_dingtalk_plugin.py @@ -26,5 +26,30 @@ def test_onboard_config_collects_dingtalk_settings(monkeypatch) -> None: } +def _accept_defaults(monkeypatch) -> None: + monkeypatch.setattr( + plugin.bub_inquirer, "ask_text", lambda message, default="": default + ) + monkeypatch.setattr(plugin.bub_inquirer, "ask_secret", lambda message: "") + monkeypatch.setattr( + plugin.bub_inquirer, + "ask_select", + lambda message, choices, default="": default, + ) + + +def test_onboard_config_keeps_existing_dingtalk_settings(monkeypatch) -> None: + _accept_defaults(monkeypatch) + existing = { + "client_id": "client-id", + "client_secret": "client-secret", + "allow_users": "staff-1", + } + + assert plugin.onboard_config( + {"enabled_channels": "dingtalk", "dingtalk": existing} + ) == {"dingtalk": existing} + + def test_onboard_config_skips_when_dingtalk_is_disabled() -> None: assert plugin.onboard_config({"enabled_channels": "discord"}) is None diff --git a/packages/bub-discord/src/bub_discord/plugin.py b/packages/bub-discord/src/bub_discord/plugin.py index 32e924d..5f48d1b 100644 --- a/packages/bub-discord/src/bub_discord/plugin.py +++ b/packages/bub-discord/src/bub_discord/plugin.py @@ -30,11 +30,25 @@ def onboard_config(current_config: dict[str, Any]) -> dict[str, Any] | None: if not _channel_enabled(current_config): return None - token = bub_inquirer.ask_secret("Discord bot token") - allow_users = bub_inquirer.ask_text("Discord allowed users (optional)") - allow_channels = bub_inquirer.ask_text("Discord allowed channels (optional)") - command_prefix = bub_inquirer.ask_text("Discord command prefix", default="!") - proxy = bub_inquirer.ask_text("Discord proxy (optional)") + current = current_config.get(CHANNEL_NAME) + config = current if isinstance(current, dict) else {} + token = bub_inquirer.ask_secret("Discord bot token") or str( + config.get("token") or "" + ) + allow_users = bub_inquirer.ask_text( + "Discord allowed users (optional)", + default=str(config.get("allow_users") or ""), + ) + allow_channels = bub_inquirer.ask_text( + "Discord allowed channels (optional)", + default=str(config.get("allow_channels") or ""), + ) + command_prefix = bub_inquirer.ask_text( + "Discord command prefix", default=str(config.get("command_prefix") or "!") + ) + proxy = bub_inquirer.ask_text( + "Discord proxy (optional)", default=str(config.get("proxy") or "") + ) return { CHANNEL_NAME: { diff --git a/packages/bub-discord/tests/test_discord_plugin.py b/packages/bub-discord/tests/test_discord_plugin.py index 93ff4d6..a351a60 100644 --- a/packages/bub-discord/tests/test_discord_plugin.py +++ b/packages/bub-discord/tests/test_discord_plugin.py @@ -31,6 +31,33 @@ def test_onboard_config_collects_discord_settings(monkeypatch) -> None: } +def _accept_defaults(monkeypatch) -> None: + monkeypatch.setattr( + plugin.bub_inquirer, "ask_text", lambda message, default="": default + ) + monkeypatch.setattr(plugin.bub_inquirer, "ask_secret", lambda message: "") + monkeypatch.setattr( + plugin.bub_inquirer, + "ask_select", + lambda message, choices, default="": default, + ) + + +def test_onboard_config_keeps_existing_discord_settings(monkeypatch) -> None: + _accept_defaults(monkeypatch) + existing = { + "token": "token", + "allow_users": "user-1", + "allow_channels": "channel-1", + "command_prefix": "?", + "proxy": "http://proxy", + } + + assert plugin.onboard_config( + {"enabled_channels": "discord", "discord": existing} + ) == {"discord": existing} + + def test_onboard_config_skips_when_discord_is_disabled() -> None: assert plugin.onboard_config({"enabled_channels": "feishu"}) is None diff --git a/packages/bub-feishu/src/bub_feishu/plugin.py b/packages/bub-feishu/src/bub_feishu/plugin.py index c4b0983..f3c60f8 100644 --- a/packages/bub-feishu/src/bub_feishu/plugin.py +++ b/packages/bub-feishu/src/bub_feishu/plugin.py @@ -30,17 +30,38 @@ def onboard_config(current_config: dict[str, Any]) -> dict[str, Any] | None: if not _channel_enabled(current_config): return None - app_id = bub_inquirer.ask_text("Feishu app ID") - app_secret = bub_inquirer.ask_secret("Feishu app secret") - verification_token = bub_inquirer.ask_secret("Feishu verification token") - encrypt_key = bub_inquirer.ask_secret("Feishu encrypt key") - allow_users = bub_inquirer.ask_text("Feishu allowed users (optional)") - allow_chats = bub_inquirer.ask_text("Feishu allowed chats (optional)") - bot_open_id = bub_inquirer.ask_text("Feishu bot open ID (optional)") + current = current_config.get(CHANNEL_NAME) + config = current if isinstance(current, dict) else {} + app_id = bub_inquirer.ask_text( + "Feishu app ID", default=str(config.get("app_id") or "") + ) + app_secret = bub_inquirer.ask_secret("Feishu app secret") or str( + config.get("app_secret") or "" + ) + verification_token = bub_inquirer.ask_secret("Feishu verification token") or str( + config.get("verification_token") or "" + ) + encrypt_key = bub_inquirer.ask_secret("Feishu encrypt key") or str( + config.get("encrypt_key") or "" + ) + allow_users = bub_inquirer.ask_text( + "Feishu allowed users (optional)", + default=str(config.get("allow_users") or ""), + ) + allow_chats = bub_inquirer.ask_text( + "Feishu allowed chats (optional)", + default=str(config.get("allow_chats") or ""), + ) + bot_open_id = bub_inquirer.ask_text( + "Feishu bot open ID (optional)", + default=str(config.get("bot_open_id") or ""), + ) + log_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] + log_level_default = str(config.get("log_level") or "INFO").upper() log_level = bub_inquirer.ask_select( "Feishu log level", - choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], - default="INFO", + choices=log_levels, + default=log_level_default if log_level_default in log_levels else "INFO", ) return { diff --git a/packages/bub-feishu/tests/test_feishu_plugin.py b/packages/bub-feishu/tests/test_feishu_plugin.py index 30ade51..583c86d 100644 --- a/packages/bub-feishu/tests/test_feishu_plugin.py +++ b/packages/bub-feishu/tests/test_feishu_plugin.py @@ -39,6 +39,36 @@ def test_onboard_config_collects_feishu_settings(monkeypatch) -> None: } +def _accept_defaults(monkeypatch) -> None: + monkeypatch.setattr( + plugin.bub_inquirer, "ask_text", lambda message, default="": default + ) + monkeypatch.setattr(plugin.bub_inquirer, "ask_secret", lambda message: "") + monkeypatch.setattr( + plugin.bub_inquirer, + "ask_select", + lambda message, choices, default="": default, + ) + + +def test_onboard_config_keeps_existing_feishu_settings(monkeypatch) -> None: + _accept_defaults(monkeypatch) + existing = { + "app_id": "app-id", + "app_secret": "app-secret", + "verification_token": "verification-token", + "encrypt_key": "encrypt-key", + "allow_users": "user-1", + "allow_chats": "chat-1", + "bot_open_id": "bot-open-id", + "log_level": "DEBUG", + } + + assert plugin.onboard_config( + {"enabled_channels": "feishu", "feishu": existing} + ) == {"feishu": existing} + + def test_onboard_config_skips_when_feishu_is_disabled() -> None: assert plugin.onboard_config({"enabled_channels": "discord"}) is None diff --git a/packages/bub-qq/src/bub_qq/plugin.py b/packages/bub-qq/src/bub_qq/plugin.py index 810612d..e9835ee 100644 --- a/packages/bub-qq/src/bub_qq/plugin.py +++ b/packages/bub-qq/src/bub_qq/plugin.py @@ -203,9 +203,7 @@ def after_llm_call( @hookimpl -def after_tool_call( - call: ToolCall, result: ToolCallResult, state: TurnState -) -> None: +def after_tool_call(call: ToolCall, result: ToolCallResult, state: TurnState) -> None: qq_state = _qq_state(state) if qq_state is None: return @@ -241,7 +239,8 @@ def onboard_config(current_config: dict[str, Any]) -> dict[str, Any] | None: "QQ app ID", default=str(config.get("appid") or ""), ), - "secret": bub_inquirer.ask_secret("QQ secret"), + "secret": bub_inquirer.ask_secret("QQ secret") + or str(config.get("secret") or ""), "receive_mode": bub_inquirer.ask_select( "QQ receive mode", choices=RECEIVE_MODES, diff --git a/packages/bub-qq/tests/test_qq_plugin.py b/packages/bub-qq/tests/test_qq_plugin.py index 7d3cfeb..b51b64b 100644 --- a/packages/bub-qq/tests/test_qq_plugin.py +++ b/packages/bub-qq/tests/test_qq_plugin.py @@ -32,5 +32,30 @@ def test_onboard_config_collects_qq_settings(monkeypatch) -> None: } +def _accept_defaults(monkeypatch) -> None: + monkeypatch.setattr( + plugin.bub_inquirer, "ask_text", lambda message, default="": default + ) + monkeypatch.setattr(plugin.bub_inquirer, "ask_secret", lambda message: "") + monkeypatch.setattr( + plugin.bub_inquirer, + "ask_select", + lambda message, choices, default="": default, + ) + + +def test_onboard_config_keeps_existing_qq_settings(monkeypatch) -> None: + _accept_defaults(monkeypatch) + existing = { + "appid": "app-id", + "secret": "secret", + "receive_mode": "websocket", + } + + assert plugin.onboard_config({"enabled_channels": "qq", "qq": existing}) == { + "qq": existing + } + + def test_onboard_config_skips_when_qq_is_disabled() -> None: assert plugin.onboard_config({"enabled_channels": "wecom"}) is None diff --git a/packages/bub-wecom/src/bub_wecom/plugin.py b/packages/bub-wecom/src/bub_wecom/plugin.py index 5f9d6c5..0591cf7 100644 --- a/packages/bub-wecom/src/bub_wecom/plugin.py +++ b/packages/bub-wecom/src/bub_wecom/plugin.py @@ -59,7 +59,8 @@ def onboard_config(current_config: dict[str, Any]) -> dict[str, Any] | None: "WeCom bot ID", default=str(config.get("bot_id") or ""), ), - "secret": bub_inquirer.ask_secret("WeCom secret"), + "secret": bub_inquirer.ask_secret("WeCom secret") + or str(config.get("secret") or ""), "websocket_url": bub_inquirer.ask_text( "WeCom websocket URL", default=str( diff --git a/packages/bub-wecom/tests/test_wecom_plugin.py b/packages/bub-wecom/tests/test_wecom_plugin.py index 4bf0a73..d887613 100644 --- a/packages/bub-wecom/tests/test_wecom_plugin.py +++ b/packages/bub-wecom/tests/test_wecom_plugin.py @@ -41,6 +41,31 @@ def test_onboard_config_collects_wecom_settings(monkeypatch) -> None: } +def test_onboard_config_keeps_existing_wecom_settings(monkeypatch) -> None: + monkeypatch.setattr( + plugin.bub_inquirer, "ask_text", lambda message, default="": default + ) + monkeypatch.setattr(plugin.bub_inquirer, "ask_secret", lambda message: "") + monkeypatch.setattr( + plugin.bub_inquirer, + "ask_select", + lambda message, choices, default="": default, + ) + existing = { + "bot_id": "bot-id", + "secret": "secret", + "websocket_url": "wss://example.test/ws", + "dm_policy": "allowlist", + "allow_from": "alice", + "group_policy": "disabled", + "group_allow_from": "room-1", + } + + assert plugin.onboard_config({"enabled_channels": "wecom", "wecom": existing}) == { + "wecom": existing + } + + def test_onboard_config_skips_when_wecom_is_disabled() -> None: assert plugin.onboard_config({"enabled_channels": "qq"}) is None diff --git a/uv.lock b/uv.lock index e59940a..6b9f2b9 100644 --- a/uv.lock +++ b/uv.lock @@ -371,8 +371,8 @@ wheels = [ [[package]] name = "bub" -version = "0.5.1.dev15+g085dcd9ab" -source = { git = "https://github.com/bubbuild/bub.git#085dcd9ab558cc05c9ccda9bb264c9e643552cb6" } +version = "0.5.1" +source = { git = "https://github.com/bubbuild/bub.git#06aadff98d3f803046e811ebe95a731acbd5adfa" } dependencies = [ { name = "aiohttp" }, { name = "any-llm-sdk" },