@lobehub/lobehub 2.0.0-next.311 → 2.0.0-next.313

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 (151) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/apps/desktop/src/main/controllers/AuthCtr.ts +75 -7
  3. package/changelog/v1.json +21 -0
  4. package/docs/usage/providers/internlm.mdx +2 -2
  5. package/docs/usage/providers/internlm.zh-CN.mdx +3 -3
  6. package/e2e/README.md +1 -1
  7. package/e2e/src/features/community/detail-pages.feature +2 -2
  8. package/e2e/src/features/community/interactions.feature +5 -5
  9. package/e2e/src/features/community/smoke.feature +1 -1
  10. package/e2e/src/steps/community/detail-pages.steps.ts +6 -4
  11. package/e2e/src/steps/community/interactions.steps.ts +3 -3
  12. package/locales/en-US/error.json +10 -1
  13. package/locales/en-US/subscription.json +1 -1
  14. package/locales/zh-CN/desktop-onboarding.json +5 -0
  15. package/locales/zh-CN/error.json +10 -1
  16. package/locales/zh-CN/subscription.json +1 -1
  17. package/package.json +1 -1
  18. package/packages/agent-runtime/src/agents/GeneralChatAgent.ts +14 -2
  19. package/packages/agent-runtime/src/agents/__tests__/GeneralChatAgent.test.ts +275 -1
  20. package/packages/builtin-tool-agent-builder/src/systemRole.ts +9 -0
  21. package/packages/builtin-tool-cloud-sandbox/package.json +1 -0
  22. package/packages/builtin-tool-cloud-sandbox/src/ExecutionRuntime/index.ts +105 -134
  23. package/packages/builtin-tool-cloud-sandbox/src/executor/index.ts +254 -0
  24. package/packages/builtin-tool-cloud-sandbox/src/index.ts +1 -0
  25. package/packages/builtin-tool-cloud-sandbox/src/types/api.ts +22 -0
  26. package/packages/builtin-tool-cloud-sandbox/src/types/index.ts +4 -0
  27. package/packages/builtin-tool-cloud-sandbox/src/types/params.ts +85 -0
  28. package/packages/builtin-tool-cloud-sandbox/src/types/service.ts +48 -0
  29. package/packages/builtin-tool-cloud-sandbox/src/{types.ts → types/state.ts} +0 -23
  30. package/packages/builtin-tool-memory/src/manifest.ts +5 -5
  31. package/packages/editor-runtime/src/__tests__/EditorRuntime.real.test.ts +1 -1
  32. package/packages/editor-runtime/src/__tests__/EditorRuntime.test.ts +1 -1
  33. package/packages/electron-client-ipc/src/events/index.ts +5 -1
  34. package/packages/electron-client-ipc/src/events/remoteServer.ts +23 -0
  35. package/packages/memory-user-memory/src/schemas/index.ts +0 -1
  36. package/packages/model-bank/src/modelProviders/internlm.ts +1 -1
  37. package/packages/model-runtime/src/core/RouterRuntime/createRuntime.ts +5 -15
  38. package/packages/model-runtime/src/providers/internlm/index.test.ts +15 -15
  39. package/packages/model-runtime/src/providers/internlm/index.ts +1 -1
  40. package/packages/types/src/tool/intervention.ts +4 -2
  41. package/packages/types/src/user/preference.ts +1 -0
  42. package/public/favicon-32x-32-error.ico +0 -0
  43. package/public/favicon-32x32-done-dev.ico +0 -0
  44. package/public/favicon-32x32-done.ico +0 -0
  45. package/public/favicon-32x32-error-dev.ico +0 -0
  46. package/public/favicon-32x32-progress-dev.ico +0 -0
  47. package/public/favicon-32x32-progress.ico +0 -0
  48. package/public/favicon-done-dev.ico +0 -0
  49. package/public/favicon-done.ico +0 -0
  50. package/public/favicon-error-dev.ico +0 -0
  51. package/public/favicon-error.ico +0 -0
  52. package/public/favicon-progress-dev.ico +0 -0
  53. package/public/favicon-progress.ico +0 -0
  54. package/src/app/[variants]/(desktop)/desktop-onboarding/features/LoginStep.tsx +84 -26
  55. package/src/app/[variants]/(main)/_layout/DesktopAutoOidcOnFirstOpen.tsx +4 -0
  56. package/src/app/[variants]/(main)/agent/profile/features/Header/AgentPublishButton/PublishResultModal.tsx +1 -1
  57. package/src/app/[variants]/(main)/community/(detail)/_layout/Header.tsx +15 -3
  58. package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Overview/TagList.tsx +1 -1
  59. package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Related/index.tsx +2 -2
  60. package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/SystemRole/TagList.tsx +1 -1
  61. package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/SystemRole/index.tsx +1 -1
  62. package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Header.tsx +2 -2
  63. package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Sidebar/ActionButton/AddAgent.tsx +1 -1
  64. package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Sidebar/ActionButton/index.tsx +1 -1
  65. package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Sidebar/Related/index.tsx +2 -2
  66. package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/StatusPage/index.tsx +2 -2
  67. package/src/app/[variants]/(main)/community/(detail)/user/features/UserAgentCard.tsx +2 -2
  68. package/src/app/[variants]/(main)/community/(detail)/user/features/UserFavoriteAgents.tsx +1 -1
  69. package/src/app/[variants]/(main)/community/(list)/(home)/index.tsx +2 -2
  70. package/src/app/[variants]/(main)/community/(list)/(home)/loading.tsx +1 -1
  71. package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/Client.tsx +5 -1
  72. package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/features/Category/index.tsx +1 -1
  73. package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/features/List/Item.tsx +1 -1
  74. package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/features/MarketSourceSwitch.tsx +1 -1
  75. package/src/app/[variants]/(main)/community/_layout/Sidebar/Header/Nav.tsx +2 -2
  76. package/src/app/[variants]/(main)/home/features/CommunityAgents/List.tsx +1 -1
  77. package/src/app/[variants]/(main)/home/features/CommunityAgents/index.tsx +1 -1
  78. package/src/app/[variants]/(mobile)/_layout/index.tsx +1 -1
  79. package/src/app/[variants]/(mobile)/router/mobileRouter.config.tsx +6 -6
  80. package/src/app/[variants]/router/desktopRouter.config.tsx +8 -8
  81. package/src/app/[variants]/share/t/[id]/_layout/index.tsx +1 -1
  82. package/src/business/server/user.ts +4 -0
  83. package/src/features/CommandMenu/SearchResults.tsx +1 -1
  84. package/src/features/Conversation/Messages/Task/Actions/index.tsx +0 -2
  85. package/src/features/Conversation/Messages/Task/index.tsx +1 -1
  86. package/src/features/Conversation/Messages/Tasks/shared/ProcessingState.tsx +0 -2
  87. package/src/features/Electron/navigation/routeMetadata.ts +1 -1
  88. package/src/features/NavPanel/components/NavPanelDraggable.tsx +0 -14
  89. package/src/features/ResourceManager/components/Explorer/ItemDropdown/useFileItemDropdown.tsx +4 -3
  90. package/src/features/SharePopover/index.tsx +5 -3
  91. package/src/hooks/useAppOrigin.ts +16 -0
  92. package/src/layout/GlobalProvider/FaviconProvider.tsx +92 -0
  93. package/src/layout/GlobalProvider/index.tsx +15 -11
  94. package/src/layout/GlobalProvider/useUserStateRedirect.ts +37 -24
  95. package/src/libs/next/config/define-config.ts +1 -1
  96. package/src/libs/trusted-client/index.ts +2 -5
  97. package/src/locales/default/desktop-onboarding.ts +5 -0
  98. package/src/locales/default/error.ts +11 -0
  99. package/src/locales/default/subscription.ts +1 -1
  100. package/src/server/modules/AgentRuntime/RuntimeExecutors.ts +2 -0
  101. package/src/server/routers/lambda/user.ts +24 -10
  102. package/src/server/services/agentRuntime/AgentRuntimeService.test.ts +3 -0
  103. package/src/server/services/agentRuntime/AgentRuntimeService.ts +8 -5
  104. package/src/server/services/agentRuntime/types.ts +7 -0
  105. package/src/server/services/aiAgent/__tests__/execGroupSubAgentTask.test.ts +3 -0
  106. package/src/server/services/aiAgent/index.ts +10 -4
  107. package/src/server/services/market/index.ts +7 -0
  108. package/src/server/services/sandbox/index.ts +120 -0
  109. package/src/server/services/toolExecution/builtin.ts +12 -18
  110. package/src/server/services/toolExecution/index.ts +1 -1
  111. package/src/server/services/toolExecution/serverRuntimes/cloudSandbox.ts +31 -0
  112. package/src/server/services/toolExecution/serverRuntimes/index.ts +55 -0
  113. package/src/server/services/toolExecution/serverRuntimes/types.ts +14 -0
  114. package/src/server/services/toolExecution/serverRuntimes/webBrowsing.ts +20 -0
  115. package/src/server/services/toolExecution/types.ts +2 -0
  116. package/src/server/sitemap.test.ts +5 -5
  117. package/src/server/sitemap.ts +3 -3
  118. package/src/services/{codeInterpreter.ts → cloudSandbox.ts} +3 -3
  119. package/src/services/electron/remoteServer.ts +8 -0
  120. package/src/store/chat/agents/GroupOrchestration/__tests__/batch-exec-async-tasks.test.ts +626 -0
  121. package/src/store/chat/agents/GroupOrchestration/createGroupOrchestrationExecutors.ts +294 -0
  122. package/src/store/chat/slices/plugin/action.test.ts +0 -48
  123. package/src/store/chat/slices/plugin/actions/pluginTypes.ts +0 -131
  124. package/src/store/tool/slices/builtin/executors/index.ts +2 -0
  125. package/src/store/user/slices/settings/selectors/toolIntervention.test.ts +143 -0
  126. package/src/store/user/slices/settings/selectors/toolIntervention.ts +11 -2
  127. package/packages/memory-user-memory/src/schemas/jsonSchemas.ts +0 -37
  128. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/DetailProvider.tsx +0 -0
  129. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Capabilities/Block.tsx +0 -0
  130. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Capabilities/Knowledge.tsx +0 -0
  131. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Capabilities/KnowledgeItem.tsx +0 -0
  132. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Capabilities/PluginItem.tsx +0 -0
  133. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Capabilities/Plugins.tsx +0 -0
  134. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Capabilities/index.tsx +0 -0
  135. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Nav.tsx +0 -0
  136. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Overview/index.tsx +0 -0
  137. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Versions/index.tsx +0 -0
  138. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/index.tsx +0 -0
  139. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Sidebar/Related/Item.tsx +0 -0
  140. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Sidebar/Summary/index.tsx +0 -0
  141. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Sidebar/TocList/index.tsx +0 -0
  142. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Sidebar/index.tsx +0 -0
  143. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/index.tsx +0 -0
  144. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/loading.tsx +0 -0
  145. /package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/_layout/index.tsx +0 -0
  146. /package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/_layout/style.ts +0 -0
  147. /package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/features/Category/useCategory.tsx +0 -0
  148. /package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/features/List/TokenTag.tsx +0 -0
  149. /package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/features/List/index.tsx +0 -0
  150. /package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/index.tsx +0 -0
  151. /package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/loading.tsx +0 -0
@@ -3,6 +3,7 @@ import type {
3
3
  GroupOrchestrationExecutor,
4
4
  GroupOrchestrationExecutorOutput,
5
5
  SupervisorInstruction,
6
+ SupervisorInstructionBatchExecAsyncTasks,
6
7
  SupervisorInstructionCallAgent,
7
8
  SupervisorInstructionCallSupervisor,
8
9
  SupervisorInstructionDelegate,
@@ -589,5 +590,298 @@ export const createGroupOrchestrationExecutors = (
589
590
  };
590
591
  }
591
592
  },
593
+
594
+ /**
595
+ * batch_exec_async_tasks Executor
596
+ * Executes multiple async tasks for agents in parallel using aiAgentService with polling
597
+ *
598
+ * Flow:
599
+ * 1. Create task messages (role: 'task') for each task as placeholders
600
+ * 2. Call execSubAgentTask API for each task in parallel
601
+ * 3. Poll for all tasks completion
602
+ * 4. Update task messages with results on completion
603
+ *
604
+ * Returns: tasks_completed result
605
+ */
606
+ batch_exec_async_tasks: async (
607
+ instruction,
608
+ state,
609
+ ): Promise<GroupOrchestrationExecutorOutput> => {
610
+ const { tasks, toolMessageId } = (instruction as SupervisorInstructionBatchExecAsyncTasks)
611
+ .payload;
612
+
613
+ const sessionLogId = `${state.operationId}:batch_exec_async_tasks`;
614
+ log(`[${sessionLogId}] Executing ${tasks.length} async tasks in parallel`);
615
+
616
+ const { groupId, topicId } = messageContext;
617
+
618
+ if (!groupId || !topicId) {
619
+ log(`[${sessionLogId}] No valid context, cannot execute async tasks`, groupId, topicId);
620
+ return {
621
+ events: [] as GroupOrchestrationEvent[],
622
+ newState: state,
623
+ result: {
624
+ payload: {
625
+ results: tasks.map((t) => ({
626
+ agentId: t.agentId,
627
+ error: 'No valid context available',
628
+ success: false,
629
+ })),
630
+ },
631
+ type: 'tasks_completed',
632
+ },
633
+ };
634
+ }
635
+
636
+ // Track all tasks with their messages and thread IDs
637
+ interface TaskTracker {
638
+ agentId: string;
639
+ error?: string;
640
+ result?: string;
641
+ status: 'pending' | 'running' | 'completed' | 'failed';
642
+ task: string;
643
+ taskMessageId?: string;
644
+ threadId?: string;
645
+ timeout: number;
646
+ title?: string;
647
+ }
648
+
649
+ const taskTrackers: TaskTracker[] = tasks.map((t) => ({
650
+ agentId: t.agentId,
651
+ status: 'pending',
652
+ task: t.task,
653
+ timeout: t.timeout || 1_800_000, // Default 30 minutes
654
+ title: t.title,
655
+ }));
656
+
657
+ // 1. Create task messages for all tasks in parallel
658
+ await Promise.all(
659
+ taskTrackers.map(async (tracker, index) => {
660
+ const taskLogId = `${sessionLogId}:task-${index}`;
661
+ try {
662
+ const taskMessageResult = await get().optimisticCreateMessage(
663
+ {
664
+ agentId: tracker.agentId,
665
+ content: '',
666
+ groupId,
667
+ metadata: { instruction: tracker.task, taskTitle: tracker.title },
668
+ parentId: toolMessageId,
669
+ role: 'task',
670
+ topicId,
671
+ },
672
+ { operationId: state.operationId },
673
+ );
674
+
675
+ if (taskMessageResult) {
676
+ tracker.taskMessageId = taskMessageResult.id;
677
+ log(`[${taskLogId}] Created task message: ${tracker.taskMessageId}`);
678
+ } else {
679
+ tracker.status = 'failed';
680
+ tracker.error = 'Failed to create task message';
681
+ console.error(`[${taskLogId}] Failed to create task message`);
682
+ }
683
+ } catch (error) {
684
+ tracker.status = 'failed';
685
+ tracker.error = error instanceof Error ? error.message : 'Unknown error';
686
+ console.error(`[${taskLogId}] Error creating task message: ${error}`);
687
+ }
688
+ }),
689
+ );
690
+
691
+ // 2. Start all tasks in parallel via backend API
692
+ await Promise.all(
693
+ taskTrackers.map(async (tracker, index) => {
694
+ if (tracker.status === 'failed' || !tracker.taskMessageId) return;
695
+
696
+ const taskLogId = `${sessionLogId}:task-${index}`;
697
+ try {
698
+ const createResult = await aiAgentService.execSubAgentTask({
699
+ agentId: tracker.agentId,
700
+ groupId,
701
+ instruction: tracker.task,
702
+ parentMessageId: tracker.taskMessageId,
703
+ title: tracker.title,
704
+ topicId,
705
+ });
706
+
707
+ if (createResult.success) {
708
+ tracker.threadId = createResult.threadId;
709
+ tracker.status = 'running';
710
+ log(`[${taskLogId}] Task started with threadId: ${tracker.threadId}`);
711
+ } else {
712
+ tracker.status = 'failed';
713
+ tracker.error = createResult.error;
714
+ log(`[${taskLogId}] Failed to start task: ${createResult.error}`);
715
+ // Update task message with error
716
+ await get().optimisticUpdateMessageContent(
717
+ tracker.taskMessageId,
718
+ `Task creation failed: ${createResult.error}`,
719
+ undefined,
720
+ { operationId: state.operationId },
721
+ );
722
+ }
723
+ } catch (error) {
724
+ tracker.status = 'failed';
725
+ tracker.error = error instanceof Error ? error.message : 'Unknown error';
726
+ console.error(`[${taskLogId}] Error starting task: ${error}`);
727
+ }
728
+ }),
729
+ );
730
+
731
+ // 3. Poll for all tasks completion
732
+ const pollInterval = 3000; // 3 seconds
733
+ const startTime = Date.now();
734
+ const maxTimeout = Math.max(...taskTrackers.map((t) => t.timeout));
735
+
736
+ while (Date.now() - startTime < maxTimeout) {
737
+ // Check if operation has been cancelled
738
+ const currentOperation = get().operations[state.operationId];
739
+ if (currentOperation?.status === 'cancelled') {
740
+ console.warn(`[${sessionLogId}] Operation cancelled, stopping polling`);
741
+ return {
742
+ events: [] as GroupOrchestrationEvent[],
743
+ newState: { ...state, status: 'done' },
744
+ result: {
745
+ payload: {
746
+ results: taskTrackers.map((t) => ({
747
+ agentId: t.agentId,
748
+ error: t.status === 'running' ? 'Operation cancelled' : t.error,
749
+ result: t.result,
750
+ success: t.status === 'completed',
751
+ })),
752
+ },
753
+ type: 'tasks_completed',
754
+ },
755
+ };
756
+ }
757
+
758
+ // Check status of all running tasks
759
+ const runningTasks = taskTrackers.filter((t) => t.status === 'running');
760
+ if (runningTasks.length === 0) {
761
+ // All tasks have completed or failed
762
+ break;
763
+ }
764
+
765
+ await Promise.all(
766
+ runningTasks.map(async (tracker, index) => {
767
+ if (!tracker.threadId || !tracker.taskMessageId) return;
768
+
769
+ const taskLogId = `${sessionLogId}:task-${index}`;
770
+ try {
771
+ const status = await aiAgentService.getSubAgentTaskStatus({
772
+ threadId: tracker.threadId,
773
+ });
774
+
775
+ // Update taskDetail in message if available
776
+ if (status.taskDetail) {
777
+ get().internal_dispatchMessage(
778
+ {
779
+ id: tracker.taskMessageId,
780
+ type: 'updateMessage',
781
+ value: { taskDetail: status.taskDetail },
782
+ },
783
+ { operationId: state.operationId },
784
+ );
785
+ }
786
+
787
+ switch (status.status) {
788
+ case 'completed': {
789
+ tracker.status = 'completed';
790
+ tracker.result = status.result;
791
+ log(`[${taskLogId}] Task completed successfully`);
792
+ if (status.result) {
793
+ await get().optimisticUpdateMessageContent(
794
+ tracker.taskMessageId,
795
+ status.result,
796
+ undefined,
797
+ { operationId: state.operationId },
798
+ );
799
+ }
800
+
801
+ break;
802
+ }
803
+ case 'failed': {
804
+ tracker.status = 'failed';
805
+ tracker.error = status.error;
806
+ console.error(`[${taskLogId}] Task failed: ${status.error}`);
807
+ await get().optimisticUpdateMessageContent(
808
+ tracker.taskMessageId,
809
+ `Task failed: ${status.error}`,
810
+ undefined,
811
+ { operationId: state.operationId },
812
+ );
813
+
814
+ break;
815
+ }
816
+ case 'cancel': {
817
+ tracker.status = 'failed';
818
+ tracker.error = 'Task was cancelled';
819
+ log(`[${taskLogId}] Task was cancelled`);
820
+ await get().optimisticUpdateMessageContent(
821
+ tracker.taskMessageId,
822
+ 'Task was cancelled',
823
+ undefined,
824
+ { operationId: state.operationId },
825
+ );
826
+
827
+ break;
828
+ }
829
+ // No default
830
+ }
831
+
832
+ // Check individual task timeout
833
+ if (tracker.status === 'running' && Date.now() - startTime > tracker.timeout) {
834
+ tracker.status = 'failed';
835
+ tracker.error = `Task timeout after ${tracker.timeout}ms`;
836
+ log(`[${taskLogId}] Task timeout`);
837
+ await get().optimisticUpdateMessageContent(
838
+ tracker.taskMessageId,
839
+ `Task timeout after ${tracker.timeout}ms`,
840
+ undefined,
841
+ { operationId: state.operationId },
842
+ );
843
+ }
844
+ } catch (error) {
845
+ console.error(`[${taskLogId}] Error polling task status: ${error}`);
846
+ }
847
+ }),
848
+ );
849
+
850
+ // Wait before next poll
851
+ await sleep(pollInterval);
852
+ }
853
+
854
+ // Mark any remaining running tasks as timed out
855
+ for (const tracker of taskTrackers) {
856
+ if (tracker.status === 'running' && tracker.taskMessageId) {
857
+ tracker.status = 'failed';
858
+ tracker.error = `Task timeout after ${tracker.timeout}ms`;
859
+ await get().optimisticUpdateMessageContent(
860
+ tracker.taskMessageId,
861
+ `Task timeout after ${tracker.timeout}ms`,
862
+ undefined,
863
+ { operationId: state.operationId },
864
+ );
865
+ }
866
+ }
867
+
868
+ log(`[${sessionLogId}] All tasks completed`);
869
+
870
+ return {
871
+ events: [] as GroupOrchestrationEvent[],
872
+ newState: state,
873
+ result: {
874
+ payload: {
875
+ results: taskTrackers.map((t) => ({
876
+ agentId: t.agentId,
877
+ error: t.error,
878
+ result: t.result,
879
+ success: t.status === 'completed',
880
+ })),
881
+ },
882
+ type: 'tasks_completed',
883
+ },
884
+ };
885
+ },
592
886
  };
593
887
  };
@@ -1606,53 +1606,5 @@ describe('ChatPluginAction', () => {
1606
1606
  );
1607
1607
  });
1608
1608
  });
1609
-
1610
- describe('invokeCloudCodeInterpreterTool', () => {
1611
- it('should use optimisticUpdateToolMessage for successful result', async () => {
1612
- const mockResult = {
1613
- content: 'code interpreter result',
1614
- state: { output: 'test output' },
1615
- success: true,
1616
- };
1617
-
1618
- // Mock CloudSandboxExecutionRuntime using doMock for dynamic mocking
1619
- vi.doMock('@lobechat/builtin-tool-cloud-sandbox/executionRuntime', () => ({
1620
- CloudSandboxExecutionRuntime: class {
1621
- 'test-api' = vi.fn().mockResolvedValue(mockResult);
1622
- },
1623
- }));
1624
-
1625
- const optimisticUpdateToolMessageMock = vi.fn().mockResolvedValue(undefined);
1626
-
1627
- act(() => {
1628
- useChatStore.setState({
1629
- activeAgentId: 'session-id',
1630
- messagesMap: { [messageMapKey({ agentId: 'session-id' })]: [] },
1631
- optimisticUpdateToolMessage: optimisticUpdateToolMessageMock,
1632
- replaceMessages: vi.fn(),
1633
- messageOperationMap: {},
1634
- operations: {},
1635
- });
1636
- });
1637
-
1638
- const { result } = renderHook(() => useChatStore());
1639
-
1640
- await act(async () => {
1641
- await result.current.invokeCloudCodeInterpreterTool(messageId, payload);
1642
- });
1643
-
1644
- expect(optimisticUpdateToolMessageMock).toHaveBeenCalledWith(
1645
- messageId,
1646
- {
1647
- content: mockResult.content,
1648
- pluginError: undefined,
1649
- pluginState: mockResult.state,
1650
- },
1651
- undefined,
1652
- );
1653
-
1654
- vi.doUnmock('@lobechat/builtin-tool-cloud-sandbox/executionRuntime');
1655
- });
1656
- });
1657
1609
  });
1658
1610
  });
@@ -1,5 +1,4 @@
1
1
  /* eslint-disable sort-keys-fix/sort-keys-fix, typescript-sort-keys/interface */
2
- import { CloudSandboxIdentifier, type ExportFileState } from '@lobechat/builtin-tool-cloud-sandbox';
3
2
  import { type ChatToolPayload, type RuntimeStepContext } from '@lobechat/types';
4
3
  import { PluginErrorType } from '@lobehub/chat-plugin-sdk';
5
4
  import debug from 'debug';
@@ -14,8 +13,6 @@ import { AI_RUNTIME_OPERATION_TYPES } from '@/store/chat/slices/operation';
14
13
  import { type ChatStore } from '@/store/chat/store';
15
14
  import { useToolStore } from '@/store/tool';
16
15
  import { hasExecutor } from '@/store/tool/slices/builtin/executors';
17
- import { useUserStore } from '@/store/user';
18
- import { userProfileSelectors } from '@/store/user/slices/auth/selectors';
19
16
  import { safeParseJSON } from '@/utils/safeParseJSON';
20
17
 
21
18
  import { dbMessageSelectors } from '../../message/selectors';
@@ -42,14 +39,6 @@ export interface PluginTypesAction {
42
39
  stepContext?: RuntimeStepContext,
43
40
  ) => Promise<any>;
44
41
 
45
- /**
46
- * Invoke Cloud Code Interpreter tool
47
- */
48
- invokeCloudCodeInterpreterTool: (
49
- id: string,
50
- payload: ChatToolPayload,
51
- ) => Promise<string | undefined>;
52
-
53
42
  /**
54
43
  * Invoke default type plugin (returns data)
55
44
  */
@@ -116,11 +105,6 @@ export const pluginTypes: StateCreator<
116
105
  return await get().invokeLobehubSkillTypePlugin(id, payload);
117
106
  }
118
107
 
119
- // Check if this is Cloud Code Interpreter - route to specific handler
120
- if (payload.identifier === CloudSandboxIdentifier) {
121
- return await get().invokeCloudCodeInterpreterTool(id, payload);
122
- }
123
-
124
108
  const params = safeParseJSON(payload.arguments);
125
109
  if (!params) return { error: 'Invalid arguments', success: false };
126
110
 
@@ -245,121 +229,6 @@ export const pluginTypes: StateCreator<
245
229
  return;
246
230
  },
247
231
 
248
- invokeCloudCodeInterpreterTool: async (id, payload) => {
249
- // Get message to extract topicId
250
- const message = dbMessageSelectors.getDbMessageById(id)(get());
251
-
252
- // Get abort controller from operation
253
- const operationId = get().messageOperationMap[id];
254
- const operation = operationId ? get().operations[operationId] : undefined;
255
- const abortController = operation?.abortController;
256
-
257
- log(
258
- '[invokeCloudCodeInterpreterTool] messageId=%s, tool=%s, operationId=%s, aborted=%s',
259
- id,
260
- payload.apiName,
261
- operationId,
262
- abortController?.signal.aborted,
263
- );
264
-
265
- let data: { content: string; error?: any; state?: any; success: boolean } | undefined;
266
-
267
- try {
268
- // Import ExecutionRuntime dynamically to avoid circular dependencies
269
- const { CloudSandboxExecutionRuntime } =
270
- await import('@lobechat/builtin-tool-cloud-sandbox/executionRuntime');
271
-
272
- // Get userId from user store
273
- const userId = userProfileSelectors.userId(useUserStore.getState()) || 'anonymous';
274
-
275
- // Create runtime with context
276
- const runtime = new CloudSandboxExecutionRuntime({
277
- topicId: message?.topicId || 'default',
278
- userId,
279
- });
280
-
281
- // Parse arguments
282
- const args = safeParseJSON(payload.arguments) || {};
283
-
284
- // Call the appropriate method based on apiName
285
- const apiMethod = (runtime as Record<string, any>)[payload.apiName];
286
- if (!apiMethod) {
287
- throw new Error(`Cloud Code Interpreter API not found: ${payload.apiName}`);
288
- }
289
-
290
- data = await apiMethod.call(runtime, args);
291
- } catch (error) {
292
- console.error('[invokeCloudCodeInterpreterTool] Error:', error);
293
-
294
- const err = error as Error;
295
- if (err.message.includes('aborted') || err.message.includes('The user aborted a request.')) {
296
- log(
297
- '[invokeCloudCodeInterpreterTool] Request aborted: messageId=%s, tool=%s',
298
- id,
299
- payload.apiName,
300
- );
301
- } else {
302
- const result = await messageService.updateMessageError(id, error as any, {
303
- agentId: message?.agentId,
304
- topicId: message?.topicId,
305
- });
306
- if (result?.success && result.messages) {
307
- get().replaceMessages(result.messages, {
308
- context: {
309
- agentId: message?.agentId,
310
- topicId: message?.topicId,
311
- },
312
- });
313
- }
314
- }
315
- }
316
-
317
- if (!data) return;
318
-
319
- const context = operationId ? { operationId } : undefined;
320
-
321
- // Use optimisticUpdateToolMessage to update content and state/error in a single call
322
- await get().optimisticUpdateToolMessage(
323
- id,
324
- {
325
- content: data.content,
326
- pluginError: data.success ? undefined : data.error,
327
- pluginState: data.success ? data.state : undefined,
328
- },
329
- context,
330
- );
331
-
332
- // Handle exportFile: associate the file (already created by server) with assistant message (parent)
333
- if (payload.apiName === 'exportFile' && data.success && data.state) {
334
- const exportState = data.state as ExportFileState;
335
- // Server now creates the file record and returns fileId in the response
336
- if (exportState.fileId && exportState.filename) {
337
- try {
338
- // Associate file with the assistant message (parent of tool message)
339
- // The current message (id) is the tool message, we need to attach to its parent
340
- const targetMessageId = message?.parentId || id;
341
-
342
- await messageService.addFilesToMessage(targetMessageId, [exportState.fileId], {
343
- agentId: message?.agentId,
344
- topicId: message?.topicId,
345
- });
346
-
347
- log(
348
- '[invokeCloudCodeInterpreterTool] Associated exported file with message: targetMessageId=%s, fileId=%s, filename=%s',
349
- targetMessageId,
350
- exportState.fileId,
351
- exportState.filename,
352
- );
353
- } catch (error) {
354
- // Log error but don't fail the tool execution
355
- console.error('[invokeCloudCodeInterpreterTool] Failed to save exported file:', error);
356
- }
357
- }
358
- }
359
-
360
- return data.content;
361
- },
362
-
363
232
  invokeDefaultTypePlugin: async (id, payload) => {
364
233
  const { internal_callPluginApi } = get();
365
234
 
@@ -5,6 +5,7 @@
5
5
  * Executors are registered as class instances by identifier.
6
6
  */
7
7
  import { agentBuilderExecutor } from '@lobechat/builtin-tool-agent-builder/executor';
8
+ import { cloudSandboxExecutor } from '@lobechat/builtin-tool-cloud-sandbox/executor';
8
9
  import { groupAgentBuilderExecutor } from '@lobechat/builtin-tool-group-agent-builder/executor';
9
10
  import { groupManagementExecutor } from '@lobechat/builtin-tool-group-management/executor';
10
11
  import { gtdExecutor } from '@lobechat/builtin-tool-gtd/executor';
@@ -120,6 +121,7 @@ const registerExecutors = (executors: IBuiltinToolExecutor[]): void => {
120
121
  // Register all executor instances
121
122
  registerExecutors([
122
123
  agentBuilderExecutor,
124
+ cloudSandboxExecutor,
123
125
  groupAgentBuilderExecutor,
124
126
  groupManagementExecutor,
125
127
  gtdExecutor,
@@ -0,0 +1,143 @@
1
+ import { UserStore } from '@/store/user';
2
+ import { UserState, initialState } from '@/store/user/initialState';
3
+ import { merge } from '@/utils/merge';
4
+
5
+ import { toolInterventionSelectors } from './toolIntervention';
6
+
7
+ describe('toolInterventionSelectors', () => {
8
+ describe('approvalMode', () => {
9
+ it('should return "manual" by default when no config exists', () => {
10
+ const s: UserState = merge(initialState, {
11
+ settings: {},
12
+ });
13
+
14
+ const result = toolInterventionSelectors.approvalMode(s as UserStore);
15
+
16
+ expect(result).toBe('manual');
17
+ });
18
+
19
+ it('should return "auto-run" when configured', () => {
20
+ const s: UserState = merge(initialState, {
21
+ settings: {
22
+ tool: {
23
+ humanIntervention: {
24
+ approvalMode: 'auto-run',
25
+ },
26
+ },
27
+ },
28
+ });
29
+
30
+ const result = toolInterventionSelectors.approvalMode(s as UserStore);
31
+
32
+ expect(result).toBe('auto-run');
33
+ });
34
+
35
+ it('should return "allow-list" when configured', () => {
36
+ const s: UserState = merge(initialState, {
37
+ settings: {
38
+ tool: {
39
+ humanIntervention: {
40
+ approvalMode: 'allow-list',
41
+ },
42
+ },
43
+ },
44
+ });
45
+
46
+ const result = toolInterventionSelectors.approvalMode(s as UserStore);
47
+
48
+ expect(result).toBe('allow-list');
49
+ });
50
+
51
+ it('should return "manual" when configured', () => {
52
+ const s: UserState = merge(initialState, {
53
+ settings: {
54
+ tool: {
55
+ humanIntervention: {
56
+ approvalMode: 'manual',
57
+ },
58
+ },
59
+ },
60
+ });
61
+
62
+ const result = toolInterventionSelectors.approvalMode(s as UserStore);
63
+
64
+ expect(result).toBe('manual');
65
+ });
66
+
67
+ it('should fallback to "auto-run" when approvalMode is "headless"', () => {
68
+ const s: UserState = merge(initialState, {
69
+ settings: {
70
+ tool: {
71
+ humanIntervention: {
72
+ approvalMode: 'headless' as any,
73
+ },
74
+ },
75
+ },
76
+ });
77
+
78
+ const result = toolInterventionSelectors.approvalMode(s as UserStore);
79
+
80
+ // headless is for backend async tasks only, UI should show auto-run
81
+ expect(result).toBe('auto-run');
82
+ });
83
+ });
84
+
85
+ describe('allowList', () => {
86
+ it('should return empty array by default', () => {
87
+ const s: UserState = merge(initialState, {
88
+ settings: {},
89
+ });
90
+
91
+ const result = toolInterventionSelectors.allowList(s as UserStore);
92
+
93
+ expect(result).toEqual([]);
94
+ });
95
+
96
+ it('should return configured allowList', () => {
97
+ const allowList = ['bash/bash', 'web-search/search'];
98
+ const s: UserState = merge(initialState, {
99
+ settings: {
100
+ tool: {
101
+ humanIntervention: {
102
+ allowList,
103
+ },
104
+ },
105
+ },
106
+ });
107
+
108
+ const result = toolInterventionSelectors.allowList(s as UserStore);
109
+
110
+ expect(result).toEqual(allowList);
111
+ });
112
+ });
113
+
114
+ describe('config', () => {
115
+ it('should return empty object by default', () => {
116
+ const s: UserState = merge(initialState, {
117
+ settings: {},
118
+ });
119
+
120
+ const result = toolInterventionSelectors.config(s as UserStore);
121
+
122
+ expect(result).toEqual({});
123
+ });
124
+
125
+ it('should return full humanIntervention config', () => {
126
+ const config = {
127
+ approvalMode: 'allow-list' as const,
128
+ allowList: ['bash/bash'],
129
+ };
130
+ const s: UserState = merge(initialState, {
131
+ settings: {
132
+ tool: {
133
+ humanIntervention: config,
134
+ },
135
+ },
136
+ });
137
+
138
+ const result = toolInterventionSelectors.config(s as UserStore);
139
+
140
+ expect(result).toEqual(config);
141
+ });
142
+ });
143
+ });