diff --git a/lib/features/assistant/assistant_page_composer_bar.dart b/lib/features/assistant/assistant_page_composer_bar.dart index 33e5d94c..c8363d34 100644 --- a/lib/features/assistant/assistant_page_composer_bar.dart +++ b/lib/features/assistant/assistant_page_composer_bar.dart @@ -179,6 +179,7 @@ class ComposerBarStateInternal extends State { } setState(resetSkillPickerSearchInternal); skillPickerPortalControllerInternal.show(); + refreshSkillsForPickerInternal(); WidgetsBinding.instance.addPostFrameCallback((_) { if (!mounted || !skillPickerPortalControllerInternal.isShowing) { return; @@ -187,6 +188,19 @@ class ComposerBarStateInternal extends State { }); } + void refreshSkillsForPickerInternal() { + final skillsController = widget.controller.skillsController; + if (widget.controller.skills.isNotEmpty || skillsController.loading) { + return; + } + final selectedAgentId = widget.controller.selectedAgentId.trim(); + unawaited( + skillsController.refresh( + agentId: selectedAgentId.isEmpty ? null : selectedAgentId, + ), + ); + } + @override void dispose() { widget.controller.removeListener(handleControllerChangedInternal); diff --git a/lib/features/assistant/assistant_page_composer_state_helpers.dart b/lib/features/assistant/assistant_page_composer_state_helpers.dart index cfe42607..c437e1e5 100644 --- a/lib/features/assistant/assistant_page_composer_state_helpers.dart +++ b/lib/features/assistant/assistant_page_composer_state_helpers.dart @@ -94,7 +94,7 @@ Widget buildSkillPickerOverlayForInternal( searchFocusNode: state.skillPickerSearchFocusNodeInternal, selectedSkillKeys: state.widget.selectedSkillKeys, filteredSkills: state.filteredSkillOptionsInternal(), - isLoading: false, + isLoading: state.widget.controller.skillsController.loading, hasQuery: state.skillPickerQueryInternal.trim().isNotEmpty, onQueryChanged: state.setSkillPickerQueryInternal, onToggleSkill: (skillKey) => state.widget.onToggleSkill(skillKey), diff --git a/test/features/assistant/assistant_lower_pane_test.dart b/test/features/assistant/assistant_lower_pane_test.dart index 1c7517ce..3b211c6b 100644 --- a/test/features/assistant/assistant_lower_pane_test.dart +++ b/test/features/assistant/assistant_lower_pane_test.dart @@ -481,6 +481,85 @@ void main() { expect(sendCount, 1); }); + testWidgets('skill picker shows loading state while skills refresh', ( + tester, + ) async { + final controller = AppController( + environmentOverride: const {}, + ); + addTearDown(controller.dispose); + controller.skillsController.loadingInternal = true; + + await controller.sessionsController.switchSession('unit-fixture-task-a'); + + await tester.pumpWidget( + _buildTestApp(child: _buildLowerPane(controller: controller)), + ); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(const Key('assistant-skill-picker-button'))); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 100)); + + expect(find.text('正在加载技能…'), findsOneWidget); + expect(find.text('当前没有已加载技能。'), findsNothing); + }); + + testWidgets('skill picker displays refreshed skills and filters them', ( + tester, + ) async { + final controller = AppController( + environmentOverride: const {}, + ); + addTearDown(controller.dispose); + + await controller.sessionsController.switchSession('unit-fixture-task-a'); + + await tester.pumpWidget( + _buildTestApp( + child: _buildLowerPane( + controller: controller, + availableSkills: const [ + ComposerSkillOptionInternal( + key: 'browser-automation', + label: 'Browser Automation', + description: 'Automate browsers', + sourceLabel: 'agents-skills-personal', + groupLabel: 'Agent Skills', + groupSortOrder: 1, + icon: Icons.key_rounded, + ), + ComposerSkillOptionInternal( + key: 'pdf', + label: 'PDF Writer', + description: 'Create PDF files', + sourceLabel: 'openclaw-workspace', + groupLabel: 'Workspace Skills', + groupSortOrder: 0, + icon: Icons.key_rounded, + ), + ], + ), + ), + ); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(const Key('assistant-skill-picker-button'))); + await tester.pumpAndSettle(); + + expect(find.text('Browser Automation'), findsOneWidget); + expect(find.text('PDF Writer'), findsOneWidget); + + await tester.enterText( + find.byKey(const Key('assistant-skill-picker-search')), + 'browser', + ); + await tester.pumpAndSettle(); + + expect(find.text('Browser Automation'), findsOneWidget); + expect(find.text('PDF Writer'), findsNothing); + }); + testWidgets('keeps bottom action row visible when pane height is reduced', ( tester, ) async { @@ -657,6 +736,8 @@ Widget _buildLowerPane({ required AppController controller, TextEditingController? inputController, Future Function()? onSend, + List availableSkills = + const [], }) { final composerController = inputController ?? TextEditingController(); return SurfaceCard( @@ -670,7 +751,7 @@ Widget _buildLowerPane({ modelLabel: 'gpt-5.4', modelOptions: const [], attachments: const [], - availableSkills: const [], + availableSkills: availableSkills, selectedSkillKeys: const [], onRemoveAttachment: (_) {}, onToggleSkill: (_) {},