@lobehub/lobehub 2.0.0-next.312 → 2.0.0-next.314
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.
- package/CHANGELOG.md +51 -0
- package/apps/desktop/src/main/appBrowsers.ts +4 -1
- package/apps/desktop/src/main/controllers/AuthCtr.ts +75 -7
- package/apps/desktop/src/main/controllers/BrowserWindowsCtr.ts +15 -3
- package/apps/desktop/src/main/core/browser/Browser.ts +14 -4
- package/apps/desktop/src/main/core/browser/BrowserManager.ts +7 -2
- package/changelog/v1.json +18 -0
- package/docs/usage/providers/internlm.mdx +2 -2
- package/docs/usage/providers/internlm.zh-CN.mdx +3 -3
- package/e2e/src/steps/community/detail-pages.steps.ts +2 -2
- package/e2e/src/steps/community/interactions.steps.ts +6 -6
- package/e2e/src/steps/hooks.ts +19 -3
- package/locales/en-US/error.json +10 -1
- package/locales/en-US/subscription.json +1 -1
- package/locales/zh-CN/desktop-onboarding.json +5 -0
- package/locales/zh-CN/error.json +10 -1
- package/locales/zh-CN/subscription.json +1 -1
- package/package.json +1 -1
- package/packages/agent-runtime/src/agents/GeneralChatAgent.ts +14 -2
- package/packages/agent-runtime/src/agents/__tests__/GeneralChatAgent.test.ts +275 -1
- package/packages/builtin-tool-cloud-sandbox/package.json +1 -0
- package/packages/builtin-tool-cloud-sandbox/src/ExecutionRuntime/index.ts +105 -134
- package/packages/builtin-tool-cloud-sandbox/src/executor/index.ts +254 -0
- package/packages/builtin-tool-cloud-sandbox/src/index.ts +1 -0
- package/packages/builtin-tool-cloud-sandbox/src/types/api.ts +22 -0
- package/packages/builtin-tool-cloud-sandbox/src/types/index.ts +4 -0
- package/packages/builtin-tool-cloud-sandbox/src/types/params.ts +85 -0
- package/packages/builtin-tool-cloud-sandbox/src/types/service.ts +48 -0
- package/packages/builtin-tool-cloud-sandbox/src/{types.ts → types/state.ts} +0 -23
- package/packages/builtin-tool-memory/src/manifest.ts +5 -5
- package/packages/desktop-bridge/src/index.ts +5 -0
- package/packages/editor-runtime/src/__tests__/EditorRuntime.real.test.ts +1 -1
- package/packages/editor-runtime/src/__tests__/EditorRuntime.test.ts +1 -1
- package/packages/electron-client-ipc/src/events/index.ts +5 -1
- package/packages/electron-client-ipc/src/events/remoteServer.ts +23 -0
- package/packages/electron-client-ipc/src/types/window.ts +3 -2
- package/packages/memory-user-memory/src/schemas/index.ts +0 -1
- package/packages/model-bank/src/modelProviders/internlm.ts +1 -1
- package/packages/model-runtime/src/core/RouterRuntime/createRuntime.ts +5 -15
- package/packages/model-runtime/src/providers/internlm/index.test.ts +15 -15
- package/packages/model-runtime/src/providers/internlm/index.ts +1 -1
- package/packages/types/src/tool/intervention.ts +4 -2
- package/packages/types/src/user/preference.ts +1 -0
- package/src/app/[variants]/(desktop)/desktop-onboarding/_layout/index.tsx +6 -3
- package/src/app/[variants]/(desktop)/desktop-onboarding/components/OnboardingFooterActions.tsx +38 -0
- package/src/app/[variants]/(desktop)/desktop-onboarding/features/DataModeStep.tsx +19 -14
- package/src/app/[variants]/(desktop)/desktop-onboarding/features/LoginStep.tsx +121 -29
- package/src/app/[variants]/(desktop)/desktop-onboarding/features/PermissionsStep.tsx +19 -14
- package/src/app/[variants]/(desktop)/desktop-onboarding/index.tsx +8 -7
- package/src/app/[variants]/(main)/_layout/DesktopAutoOidcOnFirstOpen.tsx +4 -0
- package/src/app/manifest.ts +1 -1
- package/src/business/server/user.ts +4 -0
- package/src/features/Conversation/Messages/Task/Actions/index.tsx +0 -2
- package/src/features/Conversation/Messages/Task/index.tsx +1 -1
- package/src/features/Conversation/Messages/Tasks/shared/ProcessingState.tsx +0 -2
- package/src/features/Electron/titlebar/NavigationBar.tsx +1 -2
- package/src/features/NavPanel/components/NavPanelDraggable.tsx +0 -14
- package/src/features/ResourceManager/components/Explorer/ItemDropdown/useFileItemDropdown.tsx +4 -3
- package/src/features/SharePopover/index.tsx +5 -3
- package/src/hooks/useAppOrigin.ts +16 -0
- package/src/layout/GlobalProvider/useUserStateRedirect.ts +37 -24
- package/src/libs/trusted-client/index.ts +2 -5
- package/src/locales/default/desktop-onboarding.ts +5 -0
- package/src/locales/default/error.ts +11 -0
- package/src/locales/default/subscription.ts +1 -1
- package/src/server/manifest.ts +2 -2
- package/src/server/modules/AgentRuntime/RuntimeExecutors.ts +2 -0
- package/src/server/routers/lambda/user.ts +24 -10
- package/src/server/services/agentRuntime/AgentRuntimeService.test.ts +3 -0
- package/src/server/services/agentRuntime/AgentRuntimeService.ts +8 -5
- package/src/server/services/agentRuntime/types.ts +7 -0
- package/src/server/services/aiAgent/__tests__/execGroupSubAgentTask.test.ts +3 -0
- package/src/server/services/aiAgent/index.ts +10 -4
- package/src/server/services/market/index.ts +20 -0
- package/src/server/services/sandbox/index.ts +186 -0
- package/src/server/services/toolExecution/builtin.ts +12 -18
- package/src/server/services/toolExecution/index.ts +1 -1
- package/src/server/services/toolExecution/serverRuntimes/cloudSandbox.ts +38 -0
- package/src/server/services/toolExecution/serverRuntimes/index.ts +55 -0
- package/src/server/services/toolExecution/serverRuntimes/types.ts +14 -0
- package/src/server/services/toolExecution/serverRuntimes/webBrowsing.ts +20 -0
- package/src/server/services/toolExecution/types.ts +2 -0
- package/src/services/{codeInterpreter.ts → cloudSandbox.ts} +3 -3
- package/src/services/electron/remoteServer.ts +8 -0
- package/src/services/electron/system.ts +5 -5
- package/src/store/chat/agents/GroupOrchestration/__tests__/batch-exec-async-tasks.test.ts +626 -0
- package/src/store/chat/agents/GroupOrchestration/createGroupOrchestrationExecutors.ts +294 -0
- package/src/store/chat/slices/plugin/action.test.ts +0 -48
- package/src/store/chat/slices/plugin/actions/pluginTypes.ts +0 -131
- package/src/store/tool/slices/builtin/executors/index.ts +2 -0
- package/src/store/user/slices/settings/selectors/toolIntervention.test.ts +143 -0
- package/src/store/user/slices/settings/selectors/toolIntervention.ts +11 -2
- package/packages/memory-user-memory/src/schemas/jsonSchemas.ts +0 -37
|
@@ -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
|
+
});
|