From 9e407bf0908ad4b7ea8938e02d3dad939d516694 Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Fri, 10 Apr 2026 14:59:39 +0800 Subject: [PATCH] Clean up bridge provider dialog copy --- ...pp_controller_desktop_runtime_helpers.dart | 27 ++++++++++++ ..._desktop_single_agent_status_messages.dart | 24 +++++------ ...pp_controller_desktop_thread_sessions.dart | 14 +++--- .../assistant/assistant_page_components.dart | 28 +++++++----- .../assistant_page_components_core.dart | 4 +- .../assistant_page_composer_bar.dart | 2 +- .../assistant_page_tooltip_labels.dart | 4 +- .../assistant_focus_panel_previews.dart | 4 +- ...er_ai_gateway_chat_suite_single_agent.dart | 4 +- .../app_controller_core_flow_test.dart | 43 +++++++++++++++++-- ...cution_target_switch_suite_connection.dart | 10 ++--- ..._execution_target_switch_suite_thread.dart | 6 +-- 12 files changed, 119 insertions(+), 51 deletions(-) diff --git a/lib/app/app_controller_desktop_runtime_helpers.dart b/lib/app/app_controller_desktop_runtime_helpers.dart index 154ccbc7..c55fe28f 100644 --- a/lib/app/app_controller_desktop_runtime_helpers.dart +++ b/lib/app/app_controller_desktop_runtime_helpers.dart @@ -218,6 +218,26 @@ extension AppControllerDesktopRuntimeHelpers on AppController { if (lowered.contains('gateway not connected') || lowered.contains('code: offline') || lowered.contains('offlin') && lowered.contains('gateway')) { + if (target == AssistantExecutionTarget.singleAgent) { + final selection = singleAgentProviderForSession( + sessionsControllerInternal.currentSessionKey, + ); + final provider = + resolvedSingleAgentProviderInternal(selection) ?? selection; + final providerLabel = provider == SingleAgentProvider.auto + ? appText('Bridge Provider', 'Bridge Provider') + : provider.label; + final address = _extractGatewayAddressFromErrorInternal(raw); + return address.isEmpty + ? appText( + '当前线程的 Bridge Provider($providerLabel)未连接。请先在设置里连接并同步后再重试。', + 'The Bridge Provider for this thread ($providerLabel) is not connected. Connect and sync it from Settings, then try again.', + ) + : appText( + '当前线程的 Bridge Provider($providerLabel)未连接:$address。请先在设置里连接并同步后再重试。', + 'The Bridge Provider for this thread ($providerLabel) is not connected: $address. Connect and sync it from Settings, then try again.', + ); + } final profile = gatewayProfileForAssistantExecutionTargetInternal(target); final address = gatewayAddressLabelInternal(profile); final targetLabel = target.label; @@ -234,6 +254,13 @@ extension AppControllerDesktopRuntimeHelpers on AppController { return raw; } + String _extractGatewayAddressFromErrorInternal(String raw) { + final match = RegExp( + r'((?:\d{1,3}\.){3}\d{1,3}:\d+|localhost:\d+|[a-zA-Z0-9.-]+:\d+)', + ).firstMatch(raw); + return match?.group(1)?.trim() ?? ''; + } + String formatAiGatewayHttpErrorInternal(int statusCode, String detail) { final base = switch (statusCode) { 400 => appText( diff --git a/lib/app/app_controller_desktop_single_agent_status_messages.dart b/lib/app/app_controller_desktop_single_agent_status_messages.dart index f7c9e0d2..6d24c288 100644 --- a/lib/app/app_controller_desktop_single_agent_status_messages.dart +++ b/lib/app/app_controller_desktop_single_agent_status_messages.dart @@ -83,12 +83,12 @@ String singleAgentUnavailableLabelDesktopInternal( )) { return detail.isEmpty ? appText( - '当前线程固定为 ${selection.label},但它在这台设备上不可用。检测到其他外部 Agent ACP 端点时不会自动改线,请切到可用的 ACP Server。', - 'This thread is pinned to ${selection.label}, but it is unavailable on this device. XWorkmate will not reroute to another external Agent ACP endpoint automatically. Switch to an available ACP Server.', + '当前线程固定为 ${selection.label},但它在这台设备上不可用。检测到其他 Bridge Provider 时不会自动改线,请手动切到可用 Provider。', + 'This thread is pinned to ${selection.label}, but it is unavailable on this device. XWorkmate will not reroute to another bridge provider automatically. Switch to an available provider manually.', ) : appText( - '当前线程固定为 ${selection.label}:$detail 检测到其他外部 Agent ACP 端点时不会自动改线,请切到可用的 ACP Server。', - 'This thread is pinned to ${selection.label}: $detail XWorkmate will not reroute to another external Agent ACP endpoint automatically. Switch to an available ACP Server.', + '当前线程固定为 ${selection.label}:$detail 检测到其他 Bridge Provider 时不会自动改线,请手动切到可用 Provider。', + 'This thread is pinned to ${selection.label}: $detail XWorkmate will not reroute to another bridge provider automatically. Switch to an available provider manually.', ); } if (controller.singleAgentNeedsAiGatewayConfigurationForSession( @@ -96,21 +96,21 @@ String singleAgentUnavailableLabelDesktopInternal( )) { return detail.isEmpty ? appText( - '当前没有可用的外部 Agent ACP 端点。请先配置外部 Agent 连接。', - 'No external Agent ACP endpoint is available. Configure an external Agent connection first.', + '当前没有可用的 Bridge Provider。请先在设置里配置并同步外部 Agent 连接。', + 'No bridge provider is available. Configure and sync an external agent connection in Settings first.', ) : appText( - '$detail 当前没有可用的外部 Agent ACP 端点。请先配置外部 Agent 连接。', - '$detail No external Agent ACP endpoint is available. Configure an external Agent connection first.', + '$detail 当前没有可用的 Bridge Provider。请先在设置里配置并同步外部 Agent 连接。', + '$detail No bridge provider is available. Configure and sync an external agent connection in Settings first.', ); } return detail.isEmpty ? appText( - '当前线程的外部 Agent ACP 连接尚未就绪。', - 'The external Agent ACP connection for this thread is not ready yet.', + '当前线程的 Bridge Provider 尚未就绪。', + 'The bridge provider for this thread is not ready yet.', ) : appText( - '当前线程的外部 Agent ACP 连接尚未就绪:$detail', - 'The external Agent ACP connection for this thread is not ready yet: $detail', + '当前线程的 Bridge Provider 尚未就绪:$detail', + 'The bridge provider for this thread is not ready yet: $detail', ); } diff --git a/lib/app/app_controller_desktop_thread_sessions.dart b/lib/app/app_controller_desktop_thread_sessions.dart index 0f3ca2ff..e52d67b4 100644 --- a/lib/app/app_controller_desktop_thread_sessions.dart +++ b/lib/app/app_controller_desktop_thread_sessions.dart @@ -351,7 +351,7 @@ extension AppControllerDesktopThreadSessions on AppController { ); final target = assistantExecutionTargetForSession(normalizedSessionKey); if (target == AssistantExecutionTarget.singleAgent) { - final primaryLabel = appText('ACP Server Local', 'ACP Server Local'); + final primaryLabel = appText('Bridge', 'Bridge'); final provider = singleAgentProviderForSession(normalizedSessionKey); final resolvedProvider = singleAgentResolvedProviderForSession( normalizedSessionKey, @@ -362,19 +362,19 @@ extension AppControllerDesktopThreadSessions on AppController { ? joinConnectionPartsInternal([resolvedProvider.label, model]) : singleAgentShouldSuggestAcpSwitchForSession(normalizedSessionKey) ? appText( - '${provider.label} 不可用,请切到可用的 ACP Server。', - '${provider.label} is unavailable. Switch to an available ACP Server.', + '${provider.label} 当前不可用,请改成 Bridge 当前可用的 Provider。', + '${provider.label} is unavailable. Switch to a provider currently advertised by the bridge.', ) : singleAgentNeedsAiGatewayConfigurationForSession( normalizedSessionKey, ) ? appText( - '没有可用的外部 Agent ACP 端点,请先配置可用的 ACP Server。', - 'No external Agent ACP endpoint is available. Configure an ACP Server first.', + '当前没有可用的 Bridge Provider。请先在设置里配置并同步可用连接。', + 'No bridge provider is currently available. Configure and sync an available upstream connection in Settings first.', ) : appText( - '当前线程的外部 Agent ACP 连接尚未就绪。', - 'The external Agent ACP connection for this thread is not ready yet.', + '当前线程的 Bridge Provider 尚未就绪。', + 'The bridge provider for this thread is not ready yet.', ); return AssistantThreadConnectionState( executionTarget: target, diff --git a/lib/features/assistant/assistant_page_components.dart b/lib/features/assistant/assistant_page_components.dart index d673b3b1..d04fefb3 100644 --- a/lib/features/assistant/assistant_page_components.dart +++ b/lib/features/assistant/assistant_page_components.dart @@ -511,10 +511,16 @@ class AssistantEmptyStateInternal extends StatelessWidget { final reconnectAvailable = controller.canQuickConnectGateway; final title = singleAgent ? connected - ? appText('开始 ACP Server 任务', 'Start an ACP Server task') + ? appText('开始智能体任务', 'Start an agent task') : singleAgentNeedsAiGateway - ? appText('先配置 ACP Server', 'Configure ACP Server first') - : appText('先准备 ACP Server', 'Prepare the ACP Server first') + ? appText( + '先配置 Bridge Provider', + 'Configure a bridge provider first', + ) + : appText( + '先准备 Bridge Provider', + 'Prepare the bridge provider first', + ) : connected ? appText('开始对话或运行任务', 'Start a chat or run a task') : connectionState.status == RuntimeConnectionStatus.error @@ -523,22 +529,22 @@ class AssistantEmptyStateInternal extends StatelessWidget { final description = singleAgent ? connected ? appText( - '当前线程通过 ACP Server 处理任务,不会建立 OpenClaw Gateway 会话。', - 'This thread runs through the ACP Server path and does not open an OpenClaw Gateway session.', + '当前线程会通过 Bridge 当前广告的 Provider 处理任务,不会建立 OpenClaw Gateway 会话。', + 'This thread runs through the provider currently advertised by the bridge and does not open an OpenClaw Gateway session.', ) : singleAgentSuggestsAcpSwitch ? appText( - '当前线程固定为 $providerLabel,但它在这台设备上不可用。请改成可用的 ACP Server。', - 'This thread is pinned to $providerLabel, but it is unavailable on this device. Switch to an available ACP Server.', + '当前线程固定为 $providerLabel,但它在这台设备上不可用。请改成 Bridge 当前可用的 Provider。', + 'This thread is pinned to $providerLabel, but it is unavailable on this device. Switch to a provider currently advertised by the bridge.', ) : singleAgentNeedsAiGateway ? appText( - '请先在 设置 -> 集成 中配置可用的外部 Agent ACP 端点,然后以 ACP Server 模式继续当前任务。', - 'Configure an external Agent ACP endpoint in Settings -> Integrations, then continue this task in ACP Server mode.', + '请先在 设置 -> 集成 中配置并同步可用的外部 Agent 连接,然后再继续当前任务。', + 'Configure and sync an available external agent connection in Settings -> Integrations before continuing this task.', ) : appText( - '当前线程的外部 Agent ACP 连接尚未就绪。请先配置 $providerLabel 对应端点。', - 'The external Agent ACP connection for this thread is not ready yet. Configure the endpoint for $providerLabel first.', + '当前线程的 Bridge Provider 尚未就绪。请先检查 $providerLabel 对应连接。', + 'The bridge provider for this thread is not ready yet. Check the connection mapped to $providerLabel first.', ) : connected ? appText( diff --git a/lib/features/assistant/assistant_page_components_core.dart b/lib/features/assistant/assistant_page_components_core.dart index 57bb7b70..7c2f3eaa 100644 --- a/lib/features/assistant/assistant_page_components_core.dart +++ b/lib/features/assistant/assistant_page_components_core.dart @@ -37,5 +37,5 @@ import 'assistant_page_composer_skill_models.dart'; import 'assistant_page_composer_skill_picker.dart'; import 'assistant_page_composer_clipboard.dart'; -// Lightweight compatibility anchor. The Composer and assistant widgets now -// live in focused part files under the same library. +// Composer and assistant widgets live in focused part files under the same +// library to keep UI ownership local to the assistant feature. diff --git a/lib/features/assistant/assistant_page_composer_bar.dart b/lib/features/assistant/assistant_page_composer_bar.dart index 886a5183..1a544d92 100644 --- a/lib/features/assistant/assistant_page_composer_bar.dart +++ b/lib/features/assistant/assistant_page_composer_bar.dart @@ -474,7 +474,7 @@ class ComposerBarStateInternal extends State { if (singleAgent) ...[ PopupMenuButton( key: const Key('assistant-single-agent-provider-button'), - tooltip: appText('单机智能体执行器', 'Single Agent Provider'), + tooltip: appText('Bridge Provider', 'Bridge Provider'), onSelected: (value) { unawaited(controller.setSingleAgentProvider(value)); }, diff --git a/lib/features/assistant/assistant_page_tooltip_labels.dart b/lib/features/assistant/assistant_page_tooltip_labels.dart index e6a9cb12..4c300d60 100644 --- a/lib/features/assistant/assistant_page_tooltip_labels.dart +++ b/lib/features/assistant/assistant_page_tooltip_labels.dart @@ -45,8 +45,8 @@ String executionTargetTooltipInternal(AssistantExecutionTarget target) => String singleAgentProviderTooltipInternal(SingleAgentProvider provider) => appText( - '单机智能体执行器: ${provider.label}', - 'Single-agent provider: ${provider.label}', + 'Bridge Provider: ${provider.label}', + 'Bridge Provider: ${provider.label}', ); String modelTooltipInternal(String modelLabel) => diff --git a/lib/widgets/assistant_focus_panel_previews.dart b/lib/widgets/assistant_focus_panel_previews.dart index 63e9ac9a..9e60baab 100644 --- a/lib/widgets/assistant_focus_panel_previews.dart +++ b/lib/widgets/assistant_focus_panel_previews.dart @@ -116,8 +116,8 @@ class SkillsFocusPreviewInternal extends StatelessWidget { message: typedController.isSingleAgentMode ? (typedController.currentSingleAgentNeedsAiGatewayConfiguration ? appText( - '当前没有可用的外部 Agent ACP 端点,请先配置 ACP Server。', - 'No external Agent ACP endpoint is available. Configure an ACP server first.', + '当前没有可用的 Bridge Provider,请先在设置里配置并同步连接。', + 'No bridge provider is available. Configure and sync a connection in Settings first.', ) : appText( '当前线程还没有已加载技能。切换 provider 后会读取该线程自己的 skills 列表。', diff --git a/test/runtime/app_controller_ai_gateway_chat_suite_single_agent.dart b/test/runtime/app_controller_ai_gateway_chat_suite_single_agent.dart index f3e2048d..38f45cd0 100644 --- a/test/runtime/app_controller_ai_gateway_chat_suite_single_agent.dart +++ b/test/runtime/app_controller_ai_gateway_chat_suite_single_agent.dart @@ -620,7 +620,7 @@ void registerAppControllerAiGatewayChatSuiteSingleAgentTestsInternal() { controller.chatMessages.any( (message) => message.role == 'assistant' && - message.text.contains('当前没有可用的外部 Agent ACP 端点'), + message.text.contains('当前没有可用的 Bridge Provider'), ), isTrue, ); @@ -684,7 +684,7 @@ void registerAppControllerAiGatewayChatSuiteSingleAgentTestsInternal() { controller.chatMessages.any( (message) => message.role == 'assistant' && - message.text.contains('当前没有可用的外部 Agent ACP 端点'), + message.text.contains('当前没有可用的 Bridge Provider'), ), isTrue, ); diff --git a/test/runtime/app_controller_core_flow_test.dart b/test/runtime/app_controller_core_flow_test.dart index ae29b565..9b6983a3 100644 --- a/test/runtime/app_controller_core_flow_test.dart +++ b/test/runtime/app_controller_core_flow_test.dart @@ -35,8 +35,11 @@ void main() { controller.currentAssistantExecutionTarget, AssistantExecutionTarget.singleAgent, ); - expect(controller.currentAssistantConnectionState.isSingleAgent, isTrue); - expect(controller.assistantConnectionStatusLabel, 'ACP Server Local'); + expect( + controller.currentAssistantConnectionState.isSingleAgent, + isTrue, + ); + expect(controller.assistantConnectionStatusLabel, 'Bridge'); }, ); @@ -63,7 +66,10 @@ void main() { controller.currentAssistantExecutionTarget, AssistantExecutionTarget.local, ); - expect(controller.currentAssistantConnectionState.isSingleAgent, isFalse); + expect( + controller.currentAssistantConnectionState.isSingleAgent, + isFalse, + ); expect(controller.assistantConnectionStatusLabel, '已连接'); expect(controller.assistantConnectionTargetLabel, '127.0.0.1:4317'); }, @@ -92,7 +98,10 @@ void main() { controller.currentAssistantExecutionTarget, AssistantExecutionTarget.remote, ); - expect(controller.currentAssistantConnectionState.isSingleAgent, isFalse); + expect( + controller.currentAssistantConnectionState.isSingleAgent, + isFalse, + ); expect(controller.assistantConnectionStatusLabel, '已连接'); expect( controller.assistantConnectionTargetLabel, @@ -100,6 +109,32 @@ void main() { ); }, ); + + test( + 'core flow 04 formats single-agent offline errors with bridge provider wording', + () async { + final controller = await createCoreFlowControllerInternal(); + addTearDown(controller.dispose); + + final sessionKey = buildDraftSessionKeyInternal(); + controller.initializeAssistantThreadContext( + sessionKey, + title: '新对话', + executionTarget: AssistantExecutionTarget.singleAgent, + ); + + await controller.switchSession(sessionKey); + await controller.setSingleAgentProvider(SingleAgentProvider.opencode); + + expect( + controller.gatewayExecutionErrorLabelInternal( + 'gateway not connected: 127.0.0.1:18789', + target: AssistantExecutionTarget.singleAgent, + ), + '当前线程的 Bridge Provider(OpenCode)未连接:127.0.0.1:18789。请先在设置里连接并同步后再重试。', + ); + }, + ); }); } diff --git a/test/runtime/app_controller_execution_target_switch_suite_connection.dart b/test/runtime/app_controller_execution_target_switch_suite_connection.dart index 6c6c6b20..c4ab7c38 100644 --- a/test/runtime/app_controller_execution_target_switch_suite_connection.dart +++ b/test/runtime/app_controller_execution_target_switch_suite_connection.dart @@ -144,10 +144,10 @@ void registerExecutionTargetSwitchConnectionTests() { RuntimeConnectionMode.remote, ); expect(gateway.disconnectCount, 1); - expect(controller.assistantConnectionStatusLabel, 'ACP Server Local'); + expect(controller.assistantConnectionStatusLabel, 'Bridge'); expect( controller.assistantConnectionTargetLabel, - '没有可用的外部 Agent ACP 端点,请先配置可用的 ACP Server。', + '当前没有可用的 Bridge Provider。请先在设置里配置并同步可用连接。', ); expect( gateway.connectedProfiles, @@ -337,7 +337,7 @@ void registerExecutionTargetSwitchConnectionTests() { controller.settings.assistantExecutionTarget, AssistantExecutionTarget.remote, ); - expect(controller.assistantConnectionStatusLabel, 'ACP Server Local'); + expect(controller.assistantConnectionStatusLabel, 'Bridge'); }, ); @@ -479,7 +479,7 @@ void registerExecutionTargetSwitchConnectionTests() { controller.assistantExecutionTarget, AssistantExecutionTarget.singleAgent, ); - expect(controller.assistantConnectionStatusLabel, 'ACP Server Local'); + expect(controller.assistantConnectionStatusLabel, 'Bridge'); expect(completed, isFalse); } finally { if (!disconnectGate.isCompleted) { @@ -497,7 +497,7 @@ void registerExecutionTargetSwitchConnectionTests() { controller.assistantExecutionTarget, AssistantExecutionTarget.singleAgent, ); - expect(controller.assistantConnectionStatusLabel, 'ACP Server Local'); + expect(controller.assistantConnectionStatusLabel, 'Bridge'); }, ); }); diff --git a/test/runtime/app_controller_execution_target_switch_suite_thread.dart b/test/runtime/app_controller_execution_target_switch_suite_thread.dart index 3a735537..043c5fdd 100644 --- a/test/runtime/app_controller_execution_target_switch_suite_thread.dart +++ b/test/runtime/app_controller_execution_target_switch_suite_thread.dart @@ -98,7 +98,7 @@ void registerExecutionTargetSwitchThreadTests() { AssistantExecutionTarget.singleAgent, ); expect(gateway.disconnectCount, 1); - expect(controller.assistantConnectionStatusLabel, 'ACP Server Local'); + expect(controller.assistantConnectionStatusLabel, 'Bridge'); expect( controller.settings.assistantExecutionTarget, AssistantExecutionTarget.local, @@ -195,10 +195,10 @@ void registerExecutionTargetSwitchThreadTests() { ); await controller.switchSession('main'); - expect(controller.assistantConnectionStatusLabel, 'ACP Server Local'); + expect(controller.assistantConnectionStatusLabel, 'Bridge'); expect( controller.assistantConnectionTargetLabel, - '没有可用的外部 Agent ACP 端点,请先配置可用的 ACP Server。', + '当前没有可用的 Bridge Provider。请先在设置里配置并同步可用连接。', ); }, );