@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
@@ -16,7 +16,12 @@ import { after } from 'next/server';
16
16
  import { v4 as uuidv4 } from 'uuid';
17
17
  import { z } from 'zod';
18
18
 
19
- import { getIsInWaitList, getReferralStatus, getSubscriptionPlan } from '@/business/server/user';
19
+ import {
20
+ getIsInviteCodeRequired,
21
+ getIsInWaitList,
22
+ getReferralStatus,
23
+ getSubscriptionPlan,
24
+ } from '@/business/server/user';
20
25
  import { MessageModel } from '@/database/models/message';
21
26
  import { SessionModel } from '@/database/models/session';
22
27
  import { UserModel, UserNotFoundError } from '@/database/models/user';
@@ -129,15 +134,23 @@ export const userRouter = router({
129
134
  };
130
135
 
131
136
  // Run user state fetch and count queries in parallel
132
- const [state, messageCount, hasExtraSession, referralStatus, subscriptionPlan, isInWaitList] =
133
- await Promise.all([
134
- getOrCreateUserState(),
135
- ctx.messageModel.countUpTo(5),
136
- ctx.sessionModel.hasMoreThanN(1),
137
- getReferralStatus(ctx.userId),
138
- getSubscriptionPlan(ctx.userId),
139
- getIsInWaitList(ctx.userId),
140
- ]);
137
+ const [
138
+ state,
139
+ messageCount,
140
+ hasExtraSession,
141
+ referralStatus,
142
+ subscriptionPlan,
143
+ isInWaitList,
144
+ isInviteCodeRequired,
145
+ ] = await Promise.all([
146
+ getOrCreateUserState(),
147
+ ctx.messageModel.countUpTo(5),
148
+ ctx.sessionModel.hasMoreThanN(1),
149
+ getReferralStatus(ctx.userId),
150
+ getSubscriptionPlan(ctx.userId),
151
+ getIsInWaitList(ctx.userId),
152
+ getIsInviteCodeRequired(ctx.userId),
153
+ ]);
141
154
 
142
155
  const hasMoreThan4Messages = messageCount > 4;
143
156
  const hasAnyMessages = messageCount > 0;
@@ -168,6 +181,7 @@ export const userRouter = router({
168
181
  referralStatus,
169
182
  subscriptionPlan,
170
183
  isInWaitList,
184
+ isInviteCodeRequired,
171
185
  isFreePlan: !subscriptionPlan || subscriptionPlan === Plans.Free,
172
186
  } satisfies UserInitializationState;
173
187
  /* eslint-enable sort-keys-fix/sort-keys-fix */
@@ -40,6 +40,9 @@ vi.mock('@/server/modules/ModelRuntime', () => ({
40
40
 
41
41
  // Mock search service to avoid server-side env access
42
42
  vi.mock('@/server/services/search', () => ({
43
+ SearchService: vi.fn().mockImplementation(() => ({
44
+ search: vi.fn(),
45
+ })),
43
46
  searchService: {
44
47
  search: vi.fn(),
45
48
  },
@@ -233,6 +233,7 @@ export class AgentRuntimeService {
233
233
  toolManifestMap,
234
234
  toolSourceMap,
235
235
  stepCallbacks,
236
+ userInterventionConfig,
236
237
  } = params;
237
238
 
238
239
  try {
@@ -261,6 +262,8 @@ export class AgentRuntimeService {
261
262
  toolManifestMap,
262
263
  toolSourceMap,
263
264
  tools,
265
+ // User intervention config for headless mode in async tasks
266
+ userInterventionConfig,
264
267
  } as Partial<AgentState>;
265
268
 
266
269
  // Use coordinator to create operation, automatically sends initialization event
@@ -329,10 +332,7 @@ export class AgentRuntimeService {
329
332
  });
330
333
 
331
334
  // Get operation state and metadata
332
- const [agentState, operationMetadata] = await Promise.all([
333
- this.coordinator.loadAgentState(operationId),
334
- this.coordinator.getOperationMetadata(operationId),
335
- ]);
335
+ const agentState = await this.coordinator.loadAgentState(operationId);
336
336
 
337
337
  if (!agentState) {
338
338
  throw new Error(`Agent state not found for operation ${operationId}`);
@@ -353,8 +353,10 @@ export class AgentRuntimeService {
353
353
  }
354
354
 
355
355
  // Create Agent and Runtime instances
356
+ // Use agentState.metadata which contains the full app context (topicId, agentId, etc.)
357
+ // operationMetadata only contains basic fields (agentConfig, modelRuntimeConfig, userId)
356
358
  const { runtime } = await this.createAgentRuntime({
357
- metadata: operationMetadata,
359
+ metadata: agentState?.metadata,
358
360
  operationId,
359
361
  stepIndex,
360
362
  });
@@ -850,6 +852,7 @@ export class AgentRuntimeService {
850
852
  stepIndex,
851
853
  streamManager: this.streamManager,
852
854
  toolExecutionService: this.toolExecutionService,
855
+ topicId: metadata?.topicId,
853
856
  userId: metadata?.userId,
854
857
  };
855
858
 
@@ -1,5 +1,6 @@
1
1
  import { type AgentRuntimeContext, type AgentState } from '@lobechat/agent-runtime';
2
2
  import { type LobeToolManifest } from '@lobechat/context-engine';
3
+ import type { UserInterventionConfig } from '@lobechat/types';
3
4
 
4
5
  // ==================== Step Lifecycle Callbacks ====================
5
6
 
@@ -90,6 +91,12 @@ export interface OperationCreationParams {
90
91
  toolSourceMap?: Record<string, 'builtin' | 'plugin' | 'mcp' | 'klavis' | 'lobehubSkill'>;
91
92
  tools?: any[];
92
93
  userId?: string;
94
+ /**
95
+ * User intervention configuration
96
+ * Controls how tools requiring approval are handled
97
+ * Use { approvalMode: 'headless' } for async tasks that should never wait for human approval
98
+ */
99
+ userInterventionConfig?: UserInterventionConfig;
93
100
  }
94
101
 
95
102
  export interface OperationCreationResult {
@@ -185,6 +185,9 @@ describe('AiAgentService.execSubAgentTask', () => {
185
185
  onAfterStep: expect.any(Function),
186
186
  onComplete: expect.any(Function),
187
187
  }),
188
+ userInterventionConfig: {
189
+ approvalMode: 'headless',
190
+ },
188
191
  });
189
192
  });
190
193
 
@@ -8,6 +8,7 @@ import type {
8
8
  ExecGroupAgentResult,
9
9
  ExecSubAgentTaskParams,
10
10
  ExecSubAgentTaskResult,
11
+ UserInterventionConfig,
11
12
  } from '@lobechat/types';
12
13
  import { ThreadStatus, ThreadType } from '@lobechat/types';
13
14
  import { nanoid } from '@lobechat/utils';
@@ -66,6 +67,11 @@ interface InternalExecAgentParams extends ExecAgentParams {
66
67
  stepCallbacks?: StepLifecycleCallbacks;
67
68
  /** Topic creation trigger source ('cron' | 'chat' | 'api') */
68
69
  trigger?: string;
70
+ /**
71
+ * User intervention configuration
72
+ * Use { approvalMode: 'headless' } for async tasks that should never wait for human approval
73
+ */
74
+ userInterventionConfig?: UserInterventionConfig;
69
75
  }
70
76
 
71
77
  /**
@@ -125,6 +131,7 @@ export class AiAgentService {
125
131
  stepCallbacks,
126
132
  trigger,
127
133
  cronJobId,
134
+ userInterventionConfig,
128
135
  } = params;
129
136
 
130
137
  // Validate that either agentId or slug is provided
@@ -231,10 +238,6 @@ export class AiAgentService {
231
238
 
232
239
  const tools = toolsResult.tools;
233
240
 
234
- // Log detailed tools generation result
235
- if (toolsResult.filteredTools && toolsResult.filteredTools.length > 0) {
236
- log('execAgent: filtered tools: %O', toolsResult.filteredTools);
237
- }
238
241
  log('execAgent: enabled tool ids: %O', toolsResult.enabledToolIds);
239
242
 
240
243
  // Get manifest map and convert from Map to Record
@@ -396,6 +399,7 @@ export class AiAgentService {
396
399
  toolSourceMap,
397
400
  tools,
398
401
  userId: this.userId,
402
+ userInterventionConfig,
399
403
  });
400
404
 
401
405
  log('execAgent: created operation %s (autoStarted: %s)', operationId, result.autoStarted);
@@ -572,12 +576,14 @@ export class AiAgentService {
572
576
 
573
577
  // 4. Delegate to execAgent with threadId in appContext and callbacks
574
578
  // The instruction will be created as user message in the Thread
579
+ // Use headless mode to skip human approval in async task execution
575
580
  const result = await this.execAgent({
576
581
  agentId,
577
582
  appContext: { groupId, threadId: thread.id, topicId },
578
583
  autoStart: true,
579
584
  prompt: instruction,
580
585
  stepCallbacks,
586
+ userInterventionConfig: { approvalMode: 'headless' },
581
587
  });
582
588
 
583
589
  log(
@@ -368,6 +368,13 @@ export class MarketService {
368
368
  return this.market.user.getUserInfo(username, options);
369
369
  }
370
370
 
371
+ /**
372
+ * Register user on market and optionally follow another user
373
+ */
374
+ async registerUser(params: { followUserId?: string; registerUserId: string }): Promise<void> {
375
+ await this.market.user.register(params);
376
+ }
377
+
371
378
  // ============================== Skills Methods ==============================
372
379
 
373
380
  /**
@@ -0,0 +1,120 @@
1
+ import { type CodeInterpreterToolName } from '@lobehub/market-sdk';
2
+ import {
3
+ type ISandboxService,
4
+ type SandboxCallToolResult,
5
+ type SandboxExportFileResult,
6
+ } from '@lobechat/builtin-tool-cloud-sandbox';
7
+ import debug from 'debug';
8
+
9
+ import { MarketService } from '@/server/services/market';
10
+
11
+ const log = debug('lobe-server:sandbox-service');
12
+
13
+ export interface ServerSandboxServiceOptions {
14
+ marketService: MarketService;
15
+ topicId: string;
16
+ userId: string;
17
+ }
18
+
19
+ /**
20
+ * Server-side Sandbox Service
21
+ *
22
+ * This service implements ISandboxService for server-side execution.
23
+ * Context (topicId, userId) is bound at construction time.
24
+ * It uses MarketService to call sandbox tools.
25
+ *
26
+ * Usage:
27
+ * - Used by BuiltinToolsExecutor when executing CloudSandbox tools on server
28
+ * - MarketService handles authentication via trustedClientToken
29
+ */
30
+ export class ServerSandboxService implements ISandboxService {
31
+ private marketService: MarketService;
32
+ private topicId: string;
33
+ private userId: string;
34
+
35
+ constructor(options: ServerSandboxServiceOptions) {
36
+ this.marketService = options.marketService;
37
+ this.topicId = options.topicId;
38
+ this.userId = options.userId;
39
+ }
40
+
41
+ /**
42
+ * Call a sandbox tool via MarketService
43
+ */
44
+ async callTool(toolName: string, params: Record<string, any>): Promise<SandboxCallToolResult> {
45
+ log('Calling sandbox tool: %s with params: %O, topicId: %s', toolName, params, this.topicId);
46
+
47
+ try {
48
+ const response = await this.marketService.getSDK().plugins.runBuildInTool(
49
+ toolName as CodeInterpreterToolName,
50
+ params as any,
51
+ { topicId: this.topicId, userId: this.userId },
52
+ );
53
+
54
+ log('Sandbox tool %s response: %O', toolName, response);
55
+
56
+ if (!response.success) {
57
+ return {
58
+ error: {
59
+ message: response.error?.message || 'Unknown error',
60
+ name: response.error?.code,
61
+ },
62
+ result: null,
63
+ sessionExpiredAndRecreated: false,
64
+ success: false,
65
+ };
66
+ }
67
+
68
+ return {
69
+ result: response.data?.result,
70
+ sessionExpiredAndRecreated: response.data?.sessionExpiredAndRecreated || false,
71
+ success: true,
72
+ };
73
+ } catch (error) {
74
+ log('Error calling sandbox tool %s: %O', toolName, error);
75
+
76
+ return {
77
+ error: {
78
+ message: (error as Error).message,
79
+ name: (error as Error).name,
80
+ },
81
+ result: null,
82
+ sessionExpiredAndRecreated: false,
83
+ success: false,
84
+ };
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Export and upload a file from sandbox
90
+ *
91
+ * Note: This is a simplified version for server-side use.
92
+ * The full implementation with S3 upload is in the tRPC router.
93
+ */
94
+ async exportAndUploadFile(path: string, filename: string): Promise<SandboxExportFileResult> {
95
+ log('Exporting file: %s from path: %s, topicId: %s', filename, path, this.topicId);
96
+
97
+ // For server-side, we need to call the exportFile tool
98
+ // The full S3 upload logic should be handled separately
99
+ // This is a basic implementation that can be extended
100
+
101
+ try {
102
+ return {
103
+ error: {
104
+ message:
105
+ 'Server-side file export not fully implemented. Use tRPC endpoint for file exports.',
106
+ },
107
+ filename,
108
+ success: false,
109
+ };
110
+ } catch (error) {
111
+ log('Error exporting file: %O', error);
112
+
113
+ return {
114
+ error: { message: (error as Error).message },
115
+ filename,
116
+ success: false,
117
+ };
118
+ }
119
+ }
120
+ }
@@ -1,28 +1,26 @@
1
- import { WebBrowsingManifest } from '@lobechat/builtin-tool-web-browsing';
2
- import { WebBrowsingExecutionRuntime } from '@lobechat/builtin-tool-web-browsing/executionRuntime';
3
1
  import { type LobeChatDatabase } from '@lobechat/database';
4
2
  import { type ChatToolPayload } from '@lobechat/types';
5
3
  import { safeParseJSON } from '@lobechat/utils';
6
4
  import debug from 'debug';
7
5
 
8
6
  import { MarketService } from '@/server/services/market';
9
- import { SearchService } from '@/server/services/search';
10
7
 
11
- import { type IToolExecutor, type ToolExecutionResult } from './types';
8
+ import { getServerRuntime, hasServerRuntime } from './serverRuntimes';
9
+ import { type IToolExecutor, type ToolExecutionContext, type ToolExecutionResult } from './types';
12
10
 
13
11
  const log = debug('lobe-server:builtin-tools-executor');
14
12
 
15
- const BuiltinToolServerRuntimes: Record<string, any> = {
16
- [WebBrowsingManifest.identifier]: WebBrowsingExecutionRuntime,
17
- };
18
-
19
13
  export class BuiltinToolsExecutor implements IToolExecutor {
20
14
  private marketService: MarketService;
21
15
 
22
16
  constructor(db: LobeChatDatabase, userId: string) {
23
17
  this.marketService = new MarketService({ userInfo: { userId } });
24
18
  }
25
- async execute(payload: ChatToolPayload): Promise<ToolExecutionResult> {
19
+
20
+ async execute(
21
+ payload: ChatToolPayload,
22
+ context: ToolExecutionContext,
23
+ ): Promise<ToolExecutionResult> {
26
24
  const { identifier, apiName, arguments: argsStr, source } = payload;
27
25
  const args = safeParseJSON(argsStr) || {};
28
26
 
@@ -43,19 +41,15 @@ export class BuiltinToolsExecutor implements IToolExecutor {
43
41
  });
44
42
  }
45
43
 
46
- // Default: original builtin runtime logic
47
- const ServerRuntime = BuiltinToolServerRuntimes[identifier];
48
-
49
- if (!ServerRuntime) {
44
+ // Use server runtime registry (handles both pre-instantiated and per-request runtimes)
45
+ if (!hasServerRuntime(identifier)) {
50
46
  throw new Error(`Builtin tool "${identifier}" is not implemented`);
51
47
  }
52
48
 
53
- const runtime = new ServerRuntime({
54
- searchService: new SearchService(),
55
- });
49
+ const runtime = getServerRuntime(identifier, context);
56
50
 
57
51
  if (!runtime[apiName]) {
58
- throw new Error(`Builtin tool ${identifier} 's ${apiName} is not implemented`);
52
+ throw new Error(`Builtin tool ${identifier}'s ${apiName} is not implemented`);
59
53
  }
60
54
 
61
55
  try {
@@ -64,7 +58,7 @@ export class BuiltinToolsExecutor implements IToolExecutor {
64
58
  const error = e as Error;
65
59
  console.error('Error executing builtin tool %s:%s: %O', identifier, apiName, error);
66
60
 
67
- return { content: error.message, error: error, success: false };
61
+ return { content: error.message, error, success: false };
68
62
  }
69
63
  }
70
64
  }
@@ -47,7 +47,7 @@ export class ToolExecutionService {
47
47
  let data: ToolExecutionResult;
48
48
  switch (typeStr) {
49
49
  case 'builtin': {
50
- data = await this.builtinToolsExecutor.execute(payload);
50
+ data = await this.builtinToolsExecutor.execute(payload, context);
51
51
  break;
52
52
  }
53
53
 
@@ -0,0 +1,31 @@
1
+ import {
2
+ CloudSandboxExecutionRuntime,
3
+ CloudSandboxIdentifier,
4
+ } from '@lobechat/builtin-tool-cloud-sandbox';
5
+
6
+ import { MarketService } from '@/server/services/market';
7
+ import { ServerSandboxService } from '@/server/services/sandbox';
8
+
9
+ import { type ServerRuntimeRegistration } from './types';
10
+
11
+ /**
12
+ * CloudSandbox Server Runtime
13
+ * Per-request runtime (needs topicId, userId)
14
+ */
15
+ export const cloudSandboxRuntime: ServerRuntimeRegistration = {
16
+ factory: (context) => {
17
+ if (!context.userId || !context.topicId) {
18
+ throw new Error('userId and topicId are required for Cloud Sandbox execution');
19
+ }
20
+
21
+ const marketService = new MarketService({ userInfo: { userId: context.userId } });
22
+ const sandboxService = new ServerSandboxService({
23
+ marketService,
24
+ topicId: context.topicId,
25
+ userId: context.userId,
26
+ });
27
+
28
+ return new CloudSandboxExecutionRuntime(sandboxService);
29
+ },
30
+ identifier: CloudSandboxIdentifier,
31
+ };
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Server Runtime Registry
3
+ *
4
+ * Central registry for all builtin tool server runtimes.
5
+ * Uses factory functions to support both:
6
+ * - Pre-instantiated runtimes (e.g., WebBrowsing - no per-request context needed)
7
+ * - Per-request runtimes (e.g., CloudSandbox - needs topicId, userId)
8
+ */
9
+ import { type ToolExecutionContext } from '../types';
10
+ import { cloudSandboxRuntime } from './cloudSandbox';
11
+ import { type ServerRuntimeFactory, type ServerRuntimeRegistration } from './types';
12
+ import { webBrowsingRuntime } from './webBrowsing';
13
+
14
+ /**
15
+ * Registry of server runtime factories by identifier
16
+ */
17
+ const serverRuntimeFactories = new Map<string, ServerRuntimeFactory>();
18
+
19
+ /**
20
+ * Register server runtimes
21
+ */
22
+ const registerRuntimes = (runtimes: ServerRuntimeRegistration[]) => {
23
+ for (const runtime of runtimes) {
24
+ serverRuntimeFactories.set(runtime.identifier, runtime.factory);
25
+ }
26
+ };
27
+
28
+ // Register all server runtimes
29
+ registerRuntimes([webBrowsingRuntime, cloudSandboxRuntime]);
30
+
31
+ // ==================== Registry API ====================
32
+
33
+ /**
34
+ * Get a server runtime by identifier
35
+ * @param identifier - The tool identifier
36
+ * @param context - Execution context (required for per-request runtimes)
37
+ */
38
+ export const getServerRuntime = (identifier: string, context: ToolExecutionContext): any => {
39
+ const factory = serverRuntimeFactories.get(identifier);
40
+ return factory?.(context);
41
+ };
42
+
43
+ /**
44
+ * Check if a server runtime exists for the given identifier
45
+ */
46
+ export const hasServerRuntime = (identifier: string): boolean => {
47
+ return serverRuntimeFactories.has(identifier);
48
+ };
49
+
50
+ /**
51
+ * Get all registered server runtime identifiers
52
+ */
53
+ export const getServerRuntimeIdentifiers = (): string[] => {
54
+ return Array.from(serverRuntimeFactories.keys());
55
+ };
@@ -0,0 +1,14 @@
1
+ import { type ToolExecutionContext } from '../types';
2
+
3
+ /**
4
+ * Factory function type for creating server runtimes
5
+ */
6
+ export type ServerRuntimeFactory = (context: ToolExecutionContext) => any;
7
+
8
+ /**
9
+ * Server runtime registration object
10
+ */
11
+ export interface ServerRuntimeRegistration {
12
+ factory: ServerRuntimeFactory;
13
+ identifier: string;
14
+ }
@@ -0,0 +1,20 @@
1
+ import { WebBrowsingManifest } from '@lobechat/builtin-tool-web-browsing';
2
+ import { WebBrowsingExecutionRuntime } from '@lobechat/builtin-tool-web-browsing/executionRuntime';
3
+
4
+ import { SearchService } from '@/server/services/search';
5
+
6
+ import { type ServerRuntimeRegistration } from './types';
7
+
8
+ // Pre-instantiated (no per-request context needed)
9
+ const runtime = new WebBrowsingExecutionRuntime({
10
+ searchService: new SearchService(),
11
+ });
12
+
13
+ /**
14
+ * WebBrowsing Server Runtime
15
+ * Pre-instantiated runtime (no per-request context needed)
16
+ */
17
+ export const webBrowsingRuntime: ServerRuntimeRegistration = {
18
+ factory: () => runtime,
19
+ identifier: WebBrowsingManifest.identifier,
20
+ };
@@ -6,6 +6,8 @@ export interface ToolExecutionContext {
6
6
  /** Server database for LobeHub Skills execution */
7
7
  serverDB?: LobeChatDatabase;
8
8
  toolManifestMap: Record<string, LobeToolManifest>;
9
+ /** Topic ID for sandbox session management */
10
+ topicId?: string;
9
11
  userId?: string;
10
12
  }
11
13
 
@@ -59,7 +59,7 @@ describe('Sitemap', () => {
59
59
  );
60
60
  expect(pageSitemap).toContainEqual(
61
61
  expect.objectContaining({
62
- url: getCanonicalUrl('/community/assistant'),
62
+ url: getCanonicalUrl('/community/agent'),
63
63
  changeFrequency: 'daily',
64
64
  priority: 0.7,
65
65
  }),
@@ -85,13 +85,13 @@ describe('Sitemap', () => {
85
85
  expect(assistantsSitemap.length).toBe(LOCALE_COUNT);
86
86
  expect(assistantsSitemap).toContainEqual(
87
87
  expect.objectContaining({
88
- url: getCanonicalUrl('/community/assistant/test-assistant'),
88
+ url: getCanonicalUrl('/community/agent/test-assistant'),
89
89
  lastModified: '2023-01-01T00:00:00.000Z',
90
90
  }),
91
91
  );
92
92
  expect(assistantsSitemap).toContainEqual(
93
93
  expect.objectContaining({
94
- url: getCanonicalUrl('/community/assistant/test-assistant?hl=zh-CN'),
94
+ url: getCanonicalUrl('/community/agent/test-assistant?hl=zh-CN'),
95
95
  lastModified: '2023-01-01T00:00:00.000Z',
96
96
  }),
97
97
  );
@@ -113,7 +113,7 @@ describe('Sitemap', () => {
113
113
  expect(firstPageSitemap.length).toBe(100 * LOCALE_COUNT); // 100 items * LOCALE_COUNT locales
114
114
  expect(firstPageSitemap).toContainEqual(
115
115
  expect.objectContaining({
116
- url: getCanonicalUrl('/community/assistant/test-assistant-0'),
116
+ url: getCanonicalUrl('/community/agent/test-assistant-0'),
117
117
  lastModified: '2023-01-01T00:00:00.000Z',
118
118
  }),
119
119
  );
@@ -123,7 +123,7 @@ describe('Sitemap', () => {
123
123
  expect(secondPageSitemap.length).toBe(50 * LOCALE_COUNT); // 50 items * LOCALE_COUNT locales
124
124
  expect(secondPageSitemap).toContainEqual(
125
125
  expect.objectContaining({
126
- url: getCanonicalUrl('/community/assistant/test-assistant-100'),
126
+ url: getCanonicalUrl('/community/agent/test-assistant-100'),
127
127
  lastModified: '2023-01-01T00:00:00.000Z',
128
128
  }),
129
129
  );
@@ -213,7 +213,7 @@ export class Sitemap {
213
213
  const sitmap = pageAssistants
214
214
  .filter((item) => item.identifier) // Filter out items with empty identifiers
215
215
  .map((item) =>
216
- this._genSitemap(urlJoin('/community/assistant', item.identifier), {
216
+ this._genSitemap(urlJoin('/community/agent', item.identifier), {
217
217
  lastModified: item?.lastModified || LAST_MODIFIED,
218
218
  }),
219
219
  );
@@ -224,7 +224,7 @@ export class Sitemap {
224
224
  const sitmap = list
225
225
  .filter((item) => item.identifier) // 过滤掉 identifier 为空的项目
226
226
  .map((item) =>
227
- this._genSitemap(urlJoin('/community/assistant', item.identifier), {
227
+ this._genSitemap(urlJoin('/community/agent', item.identifier), {
228
228
  lastModified: item?.lastModified || LAST_MODIFIED,
229
229
  }),
230
230
  );
@@ -311,7 +311,7 @@ export class Sitemap {
311
311
 
312
312
  /* ↑ cloud slot ↑ */
313
313
  ...this._genSitemap('/community', { changeFrequency: 'daily', priority: 0.7 }),
314
- ...this._genSitemap('/community/assistant', { changeFrequency: 'daily', priority: 0.7 }),
314
+ ...this._genSitemap('/community/agent', { changeFrequency: 'daily', priority: 0.7 }),
315
315
  ...this._genSitemap('/community/mcp', { changeFrequency: 'daily', priority: 0.7 }),
316
316
  ...this._genSitemap('/community/plugin', { changeFrequency: 'daily', priority: 0.7 }),
317
317
  ...this._genSitemap('/community/model', { changeFrequency: 'daily', priority: 0.7 }),
@@ -6,9 +6,9 @@ import type {
6
6
  ExportAndUploadFileResult,
7
7
  } from '@/server/routers/tools/market';
8
8
 
9
- class CodeInterpreterService {
9
+ class CloudSandboxService {
10
10
  /**
11
- * Call a cloud code interpreter tool
11
+ * Call a cloud sandbox tool
12
12
  * @param toolName - The name of the tool to call (e.g., 'runCommand', 'writeLocalFile')
13
13
  * @param params - The parameters for the tool
14
14
  * @param context - Session context containing userId and topicId for isolation
@@ -51,4 +51,4 @@ class CodeInterpreterService {
51
51
  }
52
52
  }
53
53
 
54
- export const codeInterpreterService = new CodeInterpreterService();
54
+ export const cloudSandboxService = new CloudSandboxService();
@@ -1,4 +1,5 @@
1
1
  import { type DataSyncConfig, type MarketAuthorizationParams } from '@lobechat/electron-client-ipc';
2
+
2
3
  import { ensureElectronIpc } from '@/utils/electron/ipc';
3
4
 
4
5
  class RemoteServerService {
@@ -36,6 +37,13 @@ class RemoteServerService {
36
37
  requestMarketAuthorization = async (params: MarketAuthorizationParams) => {
37
38
  return ensureElectronIpc().auth.requestMarketAuthorization(params);
38
39
  };
40
+
41
+ /**
42
+ * Cancel authorization
43
+ */
44
+ cancelAuthorization = async () => {
45
+ return ensureElectronIpc().auth.cancelAuthorization();
46
+ };
39
47
  }
40
48
 
41
49
  export const remoteServerService = new RemoteServerService();