@lobehub/lobehub 2.0.0-next.85 → 2.0.0-next.87

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/apps/desktop/src/main/modules/networkProxy/dispatcher.ts +16 -16
  3. package/apps/desktop/src/main/modules/networkProxy/tester.ts +11 -11
  4. package/apps/desktop/src/main/modules/networkProxy/urlBuilder.ts +3 -3
  5. package/apps/desktop/src/main/modules/networkProxy/validator.ts +10 -10
  6. package/changelog/v1.json +18 -0
  7. package/package.json +1 -1
  8. package/packages/agent-runtime/src/core/runtime.ts +36 -1
  9. package/packages/agent-runtime/src/types/event.ts +1 -0
  10. package/packages/agent-runtime/src/types/generalAgent.ts +16 -0
  11. package/packages/agent-runtime/src/types/instruction.ts +30 -0
  12. package/packages/agent-runtime/src/types/runtime.ts +7 -0
  13. package/packages/types/src/message/common/metadata.ts +3 -0
  14. package/packages/types/src/message/common/tools.ts +2 -2
  15. package/packages/types/src/tool/search/index.ts +8 -2
  16. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/V1Mobile/index.tsx +2 -2
  17. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/V1Mobile/useSend.ts +7 -2
  18. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/useSend.ts +15 -14
  19. package/src/app/[variants]/(main)/chat/session/features/SessionListContent/List/Item/index.tsx +2 -2
  20. package/src/components/Analytics/MainInterfaceTracker.tsx +2 -2
  21. package/src/features/ChatInput/ActionBar/STT/browser.tsx +2 -2
  22. package/src/features/ChatInput/ActionBar/STT/openai.tsx +2 -2
  23. package/src/features/Conversation/MarkdownElements/LobeThinking/Render.tsx +3 -3
  24. package/src/features/Conversation/MarkdownElements/Thinking/Render.tsx +3 -3
  25. package/src/features/Conversation/Messages/Group/Tool/Inspector/index.tsx +1 -1
  26. package/src/features/Conversation/Messages/User/index.tsx +3 -3
  27. package/src/features/Conversation/Messages/index.tsx +3 -3
  28. package/src/features/Conversation/components/AutoScroll.tsx +2 -2
  29. package/src/features/PluginsUI/Render/StandaloneType/Iframe.tsx +3 -3
  30. package/src/features/Portal/Home/Body/Plugins/ArtifactList/index.tsx +3 -3
  31. package/src/features/ShareModal/ShareText/index.tsx +3 -3
  32. package/src/services/search.ts +2 -2
  33. package/src/store/chat/agents/GeneralChatAgent.ts +98 -0
  34. package/src/store/chat/agents/__tests__/GeneralChatAgent.test.ts +366 -0
  35. package/src/store/chat/agents/__tests__/createAgentExecutors/call-llm.test.ts +1217 -0
  36. package/src/store/chat/agents/__tests__/createAgentExecutors/call-tool.test.ts +1976 -0
  37. package/src/store/chat/agents/__tests__/createAgentExecutors/finish.test.ts +453 -0
  38. package/src/store/chat/agents/__tests__/createAgentExecutors/fixtures/index.ts +4 -0
  39. package/src/store/chat/agents/__tests__/createAgentExecutors/fixtures/mockInstructions.ts +126 -0
  40. package/src/store/chat/agents/__tests__/createAgentExecutors/fixtures/mockMessages.ts +94 -0
  41. package/src/store/chat/agents/__tests__/createAgentExecutors/fixtures/mockOperations.ts +96 -0
  42. package/src/store/chat/agents/__tests__/createAgentExecutors/fixtures/mockStore.ts +138 -0
  43. package/src/store/chat/agents/__tests__/createAgentExecutors/helpers/assertions.ts +185 -0
  44. package/src/store/chat/agents/__tests__/createAgentExecutors/helpers/index.ts +3 -0
  45. package/src/store/chat/agents/__tests__/createAgentExecutors/helpers/operationTestUtils.ts +94 -0
  46. package/src/store/chat/agents/__tests__/createAgentExecutors/helpers/testExecutor.ts +139 -0
  47. package/src/store/chat/agents/__tests__/createAgentExecutors/request-human-approve.test.ts +545 -0
  48. package/src/store/chat/agents/__tests__/createAgentExecutors/resolve-aborted-tools.test.ts +686 -0
  49. package/src/store/chat/agents/createAgentExecutors.ts +313 -80
  50. package/src/store/chat/selectors.ts +1 -0
  51. package/src/store/chat/slices/aiChat/__tests__/ai-chat.integration.test.ts +667 -0
  52. package/src/store/chat/slices/aiChat/actions/__tests__/cancel-functionality.test.ts +137 -27
  53. package/src/store/chat/slices/aiChat/actions/__tests__/conversationControl.test.ts +163 -125
  54. package/src/store/chat/slices/aiChat/actions/__tests__/conversationLifecycle.test.ts +12 -2
  55. package/src/store/chat/slices/aiChat/actions/__tests__/fixtures.ts +0 -2
  56. package/src/store/chat/slices/aiChat/actions/__tests__/helpers.ts +0 -2
  57. package/src/store/chat/slices/aiChat/actions/__tests__/streamingExecutor.test.ts +286 -19
  58. package/src/store/chat/slices/aiChat/actions/__tests__/streamingStates.test.ts +0 -112
  59. package/src/store/chat/slices/aiChat/actions/conversationControl.ts +42 -99
  60. package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +90 -57
  61. package/src/store/chat/slices/aiChat/actions/generateAIGroupChat.ts +5 -25
  62. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +220 -98
  63. package/src/store/chat/slices/aiChat/actions/streamingStates.ts +0 -34
  64. package/src/store/chat/slices/aiChat/initialState.ts +0 -28
  65. package/src/store/chat/slices/aiChat/selectors.test.ts +280 -0
  66. package/src/store/chat/slices/aiChat/selectors.ts +31 -7
  67. package/src/store/chat/slices/builtinTool/actions/__tests__/localSystem.test.ts +21 -30
  68. package/src/store/chat/slices/builtinTool/actions/__tests__/search.test.ts +29 -49
  69. package/src/store/chat/slices/builtinTool/actions/interpreter.ts +83 -48
  70. package/src/store/chat/slices/builtinTool/actions/localSystem.ts +78 -28
  71. package/src/store/chat/slices/builtinTool/actions/search.ts +146 -59
  72. package/src/store/chat/slices/builtinTool/selectors.test.ts +258 -0
  73. package/src/store/chat/slices/builtinTool/selectors.ts +25 -4
  74. package/src/store/chat/slices/message/action.test.ts +134 -16
  75. package/src/store/chat/slices/message/actions/internals.ts +33 -7
  76. package/src/store/chat/slices/message/actions/optimisticUpdate.ts +85 -52
  77. package/src/store/chat/slices/message/initialState.ts +0 -10
  78. package/src/store/chat/slices/message/selectors/messageState.ts +34 -12
  79. package/src/store/chat/slices/operation/__tests__/actions.test.ts +712 -16
  80. package/src/store/chat/slices/operation/__tests__/integration.test.ts +342 -0
  81. package/src/store/chat/slices/operation/__tests__/selectors.test.ts +257 -17
  82. package/src/store/chat/slices/operation/actions.ts +218 -11
  83. package/src/store/chat/slices/operation/selectors.ts +135 -6
  84. package/src/store/chat/slices/operation/types.ts +29 -3
  85. package/src/store/chat/slices/plugin/action.test.ts +30 -322
  86. package/src/store/chat/slices/plugin/actions/internals.ts +0 -14
  87. package/src/store/chat/slices/plugin/actions/optimisticUpdate.ts +21 -19
  88. package/src/store/chat/slices/plugin/actions/pluginTypes.ts +45 -27
  89. package/src/store/chat/slices/plugin/actions/publicApi.ts +3 -4
  90. package/src/store/chat/slices/plugin/actions/workflow.ts +0 -55
  91. package/src/store/chat/slices/thread/selectors/index.ts +4 -2
  92. package/src/store/chat/slices/topic/action.ts +3 -3
  93. package/src/store/chat/slices/translate/action.ts +54 -41
  94. package/src/tools/web-browsing/ExecutionRuntime/index.ts +5 -2
  95. package/src/tools/web-browsing/Portal/Search/Footer.tsx +11 -9
@@ -220,6 +220,50 @@ describe('GeneralChatAgent', () => {
220
220
  ]);
221
221
  });
222
222
 
223
+ it('should handle invalid JSON in tool arguments gracefully', async () => {
224
+ const agent = new GeneralChatAgent({
225
+ agentConfig: { maxSteps: 100 },
226
+ sessionId: 'test-session',
227
+ modelRuntimeConfig: mockModelRuntimeConfig,
228
+ });
229
+
230
+ const toolCall: ChatToolPayload = {
231
+ id: 'call-1',
232
+ identifier: 'test-plugin',
233
+ apiName: 'test-api',
234
+ arguments: '{invalid json}', // Invalid JSON
235
+ type: 'default',
236
+ };
237
+
238
+ const state = createMockState({
239
+ toolManifestMap: {
240
+ 'test-plugin': {
241
+ identifier: 'test-plugin',
242
+ // No humanIntervention config
243
+ },
244
+ },
245
+ });
246
+
247
+ const context = createMockContext('llm_result', {
248
+ hasToolsCalling: true,
249
+ toolsCalling: [toolCall],
250
+ parentMessageId: 'msg-1',
251
+ });
252
+
253
+ // Should not throw, should proceed with call_tool (treats invalid JSON as empty args)
254
+ const result = await agent.runner(context, state);
255
+
256
+ expect(result).toEqual([
257
+ {
258
+ type: 'call_tool',
259
+ payload: {
260
+ parentMessageId: 'msg-1',
261
+ toolCalling: toolCall,
262
+ },
263
+ },
264
+ ]);
265
+ });
266
+
223
267
  it('should return request_human_approve for tools requiring intervention', async () => {
224
268
  const agent = new GeneralChatAgent({
225
269
  agentConfig: { maxSteps: 100 },
@@ -526,6 +570,328 @@ describe('GeneralChatAgent', () => {
526
570
  });
527
571
  });
528
572
 
573
+ describe('unified abort check', () => {
574
+ it('should handle abort at llm_result phase when state is interrupted', async () => {
575
+ const agent = new GeneralChatAgent({
576
+ agentConfig: { maxSteps: 100 },
577
+ sessionId: 'test-session',
578
+ modelRuntimeConfig: mockModelRuntimeConfig,
579
+ });
580
+
581
+ const toolCalls: ChatToolPayload[] = [
582
+ {
583
+ apiName: 'search',
584
+ arguments: '{"query":"test"}',
585
+ id: 'call-1',
586
+ identifier: 'lobe-web-browsing',
587
+ type: 'default',
588
+ },
589
+ ];
590
+
591
+ const state = createMockState({
592
+ status: 'interrupted', // State is interrupted
593
+ });
594
+
595
+ const context = createMockContext('llm_result', {
596
+ hasToolsCalling: true,
597
+ toolsCalling: toolCalls,
598
+ parentMessageId: 'msg-123',
599
+ });
600
+
601
+ const result = await agent.runner(context, state);
602
+
603
+ // Should handle abort and return resolve_aborted_tools
604
+ expect(result).toEqual({
605
+ type: 'resolve_aborted_tools',
606
+ payload: {
607
+ parentMessageId: 'msg-123',
608
+ toolsCalling: toolCalls,
609
+ },
610
+ });
611
+ });
612
+
613
+ it('should handle abort at tool_result phase when state is interrupted', async () => {
614
+ const agent = new GeneralChatAgent({
615
+ agentConfig: { maxSteps: 100 },
616
+ sessionId: 'test-session',
617
+ modelRuntimeConfig: mockModelRuntimeConfig,
618
+ });
619
+
620
+ const state = createMockState({
621
+ status: 'interrupted',
622
+ messages: [
623
+ {
624
+ id: 'tool-msg-1',
625
+ role: 'tool',
626
+ content: '',
627
+ plugin: {
628
+ id: 'call-1',
629
+ identifier: 'bash',
630
+ apiName: 'bash',
631
+ arguments: '{"command":"ls"}',
632
+ type: 'builtin',
633
+ },
634
+ pluginIntervention: { status: 'pending' },
635
+ } as any,
636
+ ],
637
+ });
638
+
639
+ const context = createMockContext('tool_result', {
640
+ parentMessageId: 'msg-456',
641
+ });
642
+
643
+ const result = await agent.runner(context, state);
644
+
645
+ // Should handle abort and resolve pending tools
646
+ expect(result).toEqual({
647
+ type: 'resolve_aborted_tools',
648
+ payload: {
649
+ parentMessageId: 'msg-456',
650
+ toolsCalling: [
651
+ {
652
+ id: 'call-1',
653
+ identifier: 'bash',
654
+ apiName: 'bash',
655
+ arguments: '{"command":"ls"}',
656
+ type: 'builtin',
657
+ },
658
+ ],
659
+ },
660
+ });
661
+ });
662
+
663
+ it('should return finish when state is interrupted with no tools', async () => {
664
+ const agent = new GeneralChatAgent({
665
+ agentConfig: { maxSteps: 100 },
666
+ sessionId: 'test-session',
667
+ modelRuntimeConfig: mockModelRuntimeConfig,
668
+ });
669
+
670
+ const state = createMockState({
671
+ status: 'interrupted',
672
+ });
673
+
674
+ const context = createMockContext('llm_result', {
675
+ hasToolsCalling: false,
676
+ toolsCalling: [],
677
+ parentMessageId: 'msg-789',
678
+ });
679
+
680
+ const result = await agent.runner(context, state);
681
+
682
+ // Should handle abort and return finish
683
+ expect(result).toEqual({
684
+ type: 'finish',
685
+ reason: 'user_requested',
686
+ reasonDetail: 'Operation cancelled by user',
687
+ });
688
+ });
689
+
690
+ it('should continue normal flow when state is not interrupted', async () => {
691
+ const agent = new GeneralChatAgent({
692
+ agentConfig: { maxSteps: 100 },
693
+ sessionId: 'test-session',
694
+ modelRuntimeConfig: mockModelRuntimeConfig,
695
+ });
696
+
697
+ const toolCalls: ChatToolPayload[] = [
698
+ {
699
+ apiName: 'search',
700
+ arguments: '{"query":"test"}',
701
+ id: 'call-1',
702
+ identifier: 'lobe-web-browsing',
703
+ type: 'default',
704
+ },
705
+ ];
706
+
707
+ const state = createMockState({
708
+ status: 'running', // Normal running state
709
+ });
710
+
711
+ const context = createMockContext('llm_result', {
712
+ hasToolsCalling: true,
713
+ toolsCalling: toolCalls,
714
+ parentMessageId: 'msg-999',
715
+ });
716
+
717
+ const result = await agent.runner(context, state);
718
+
719
+ // Should continue normal flow and execute tools
720
+ expect(result).toEqual([
721
+ {
722
+ type: 'call_tool',
723
+ payload: {
724
+ parentMessageId: 'msg-999',
725
+ toolCalling: toolCalls[0],
726
+ },
727
+ },
728
+ ]);
729
+ });
730
+ });
731
+
732
+ describe('unified abort check', () => {
733
+ it('should handle abort at human_abort phase when state is interrupted', async () => {
734
+ const agent = new GeneralChatAgent({
735
+ agentConfig: { maxSteps: 100 },
736
+ sessionId: 'test-session',
737
+ modelRuntimeConfig: mockModelRuntimeConfig,
738
+ });
739
+
740
+ const toolCalls: ChatToolPayload[] = [
741
+ {
742
+ apiName: 'search',
743
+ arguments: '{"query":"test"}',
744
+ id: 'call-1',
745
+ identifier: 'lobe-web-browsing',
746
+ type: 'default',
747
+ },
748
+ ];
749
+
750
+ const state = createMockState({
751
+ status: 'interrupted', // Trigger unified abort check
752
+ });
753
+
754
+ const context = createMockContext('human_abort', {
755
+ reason: 'user_cancelled',
756
+ parentMessageId: 'msg-123',
757
+ hasToolsCalling: true,
758
+ toolsCalling: toolCalls,
759
+ result: { content: '', tool_calls: [] },
760
+ });
761
+
762
+ const result = await agent.runner(context, state);
763
+
764
+ // Should handle abort via extractAbortInfo and return resolve_aborted_tools
765
+ expect(result).toEqual({
766
+ type: 'resolve_aborted_tools',
767
+ payload: {
768
+ parentMessageId: 'msg-123',
769
+ toolsCalling: toolCalls,
770
+ },
771
+ });
772
+ });
773
+ });
774
+
775
+ describe('human_abort phase', () => {
776
+ it('should return resolve_aborted_tools when there are pending tool calls', async () => {
777
+ const agent = new GeneralChatAgent({
778
+ agentConfig: { maxSteps: 100 },
779
+ sessionId: 'test-session',
780
+ modelRuntimeConfig: mockModelRuntimeConfig,
781
+ });
782
+
783
+ const toolCalls: ChatToolPayload[] = [
784
+ {
785
+ apiName: 'search',
786
+ arguments: '{"query":"test"}',
787
+ id: 'call-1',
788
+ identifier: 'lobe-web-browsing',
789
+ type: 'default',
790
+ },
791
+ {
792
+ apiName: 'getWeather',
793
+ arguments: '{"location":"NYC"}',
794
+ id: 'call-2',
795
+ identifier: 'weather-plugin',
796
+ type: 'default',
797
+ },
798
+ ];
799
+
800
+ const state = createMockState();
801
+ const context = createMockContext('human_abort', {
802
+ reason: 'user_cancelled',
803
+ parentMessageId: 'msg-123',
804
+ hasToolsCalling: true,
805
+ toolsCalling: toolCalls,
806
+ result: { content: '', tool_calls: [] },
807
+ });
808
+
809
+ const result = await agent.runner(context, state);
810
+
811
+ expect(result).toEqual({
812
+ type: 'resolve_aborted_tools',
813
+ payload: {
814
+ parentMessageId: 'msg-123',
815
+ toolsCalling: toolCalls,
816
+ },
817
+ });
818
+ });
819
+
820
+ it('should return finish when there are no tool calls', async () => {
821
+ const agent = new GeneralChatAgent({
822
+ agentConfig: { maxSteps: 100 },
823
+ sessionId: 'test-session',
824
+ modelRuntimeConfig: mockModelRuntimeConfig,
825
+ });
826
+
827
+ const state = createMockState();
828
+ const context = createMockContext('human_abort', {
829
+ reason: 'user_cancelled',
830
+ parentMessageId: 'msg-123',
831
+ hasToolsCalling: false,
832
+ toolsCalling: [],
833
+ result: { content: 'Hello', tool_calls: [] },
834
+ });
835
+
836
+ const result = await agent.runner(context, state);
837
+
838
+ expect(result).toEqual({
839
+ type: 'finish',
840
+ reason: 'user_requested',
841
+ reasonDetail: 'user_cancelled',
842
+ });
843
+ });
844
+
845
+ it('should return finish when toolsCalling is undefined', async () => {
846
+ const agent = new GeneralChatAgent({
847
+ agentConfig: { maxSteps: 100 },
848
+ sessionId: 'test-session',
849
+ modelRuntimeConfig: mockModelRuntimeConfig,
850
+ });
851
+
852
+ const state = createMockState();
853
+ const context = createMockContext('human_abort', {
854
+ reason: 'operation_cancelled',
855
+ parentMessageId: 'msg-456',
856
+ hasToolsCalling: false,
857
+ result: { content: 'Partial response', tool_calls: [] },
858
+ });
859
+
860
+ const result = await agent.runner(context, state);
861
+
862
+ expect(result).toEqual({
863
+ type: 'finish',
864
+ reason: 'user_requested',
865
+ reasonDetail: 'operation_cancelled',
866
+ });
867
+ });
868
+
869
+ it('should return finish when toolsCalling is empty array', async () => {
870
+ const agent = new GeneralChatAgent({
871
+ agentConfig: { maxSteps: 100 },
872
+ sessionId: 'test-session',
873
+ modelRuntimeConfig: mockModelRuntimeConfig,
874
+ });
875
+
876
+ const state = createMockState();
877
+ const context = createMockContext('human_abort', {
878
+ reason: 'user_cancelled',
879
+ parentMessageId: 'msg-789',
880
+ hasToolsCalling: true,
881
+ toolsCalling: [],
882
+ result: { content: '', tool_calls: [] },
883
+ });
884
+
885
+ const result = await agent.runner(context, state);
886
+
887
+ expect(result).toEqual({
888
+ type: 'finish',
889
+ reason: 'user_requested',
890
+ reasonDetail: 'user_cancelled',
891
+ });
892
+ });
893
+ });
894
+
529
895
  describe('unknown phase', () => {
530
896
  it('should return finish instruction for unknown phase', async () => {
531
897
  const agent = new GeneralChatAgent({