mirror of
https://github.com/lobehub/lobe-chat.git
synced 2026-06-17 04:55:51 +00:00
🐛 fix: bump next to 16.1.5 to fix CVE-2026-23864 (#11886)
* fix tests * bump next to 16.1.5
This commit is contained in:
+4
-4
@@ -211,7 +211,7 @@
|
||||
"@modelcontextprotocol/sdk": "^1.25.3",
|
||||
"@napi-rs/canvas": "^0.1.88",
|
||||
"@neondatabase/serverless": "^1.0.2",
|
||||
"@next/third-parties": "^16.1.4",
|
||||
"@next/third-parties": "^16.1.5",
|
||||
"@opentelemetry/exporter-jaeger": "^2.5.0",
|
||||
"@opentelemetry/winston-transport": "^0.19.0",
|
||||
"@react-pdf/renderer": "^4.3.2",
|
||||
@@ -280,7 +280,7 @@
|
||||
"model-bank": "workspace:*",
|
||||
"motion": "^12.29.0",
|
||||
"nanoid": "^5.1.6",
|
||||
"next": "^16.1.4",
|
||||
"next": "^16.1.5",
|
||||
"next-mdx-remote": "^5.0.0",
|
||||
"next-themes": "^0.4.6",
|
||||
"nextjs-toploader": "^3.9.17",
|
||||
@@ -369,8 +369,8 @@
|
||||
"@lobehub/lint": "^1.26.3",
|
||||
"@lobehub/market-types": "^1.12.3",
|
||||
"@lobehub/seo-cli": "^1.7.0",
|
||||
"@next/bundle-analyzer": "^16.1.4",
|
||||
"@next/eslint-plugin-next": "^16.1.4",
|
||||
"@next/bundle-analyzer": "^16.1.5",
|
||||
"@next/eslint-plugin-next": "^16.1.5",
|
||||
"@peculiar/webcrypto": "^1.5.0",
|
||||
"@playwright/test": "^1.58.0",
|
||||
"@prettier/sync": "^0.6.1",
|
||||
|
||||
@@ -356,6 +356,11 @@ export interface TaskStatusResult {
|
||||
currentActivity?: TaskCurrentActivity;
|
||||
/** Error message if task failed */
|
||||
error?: string;
|
||||
/**
|
||||
* Parsed UI messages from conversation-flow
|
||||
* Used for displaying intermediate steps in server task
|
||||
*/
|
||||
messages?: UIChatMessage[];
|
||||
/** Task result content (last assistant message) */
|
||||
result?: string;
|
||||
/** Current task status */
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { type AgentRuntimeContext } from '@lobechat/agent-runtime';
|
||||
import { parse } from '@lobechat/conversation-flow';
|
||||
import {
|
||||
type TaskCurrentActivity,
|
||||
type TaskStatusResult,
|
||||
@@ -251,94 +252,6 @@ const aiAgentProcedure = authedProcedure.use(serverDatabase).use(async (opts) =>
|
||||
});
|
||||
|
||||
export const aiAgentRouter = router({
|
||||
/**
|
||||
* Create Thread for client-side task execution
|
||||
*
|
||||
* This endpoint is called by desktop client when runInClient=true.
|
||||
* It creates the Thread but does NOT execute the task - execution happens on client side.
|
||||
*/
|
||||
createClientTaskThread: aiAgentProcedure
|
||||
.input(CreateClientTaskThreadSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { agentId, groupId, instruction, parentMessageId, title, topicId } = input;
|
||||
|
||||
log('createClientTaskThread: agentId=%s, groupId=%s', agentId, groupId);
|
||||
|
||||
try {
|
||||
// 1. Create Thread for isolated task execution
|
||||
const startedAt = new Date().toISOString();
|
||||
const thread = await ctx.threadModel.create({
|
||||
agentId,
|
||||
groupId,
|
||||
metadata: { clientMode: true, startedAt },
|
||||
sourceMessageId: parentMessageId,
|
||||
status: ThreadStatus.Processing,
|
||||
title,
|
||||
topicId,
|
||||
type: ThreadType.Isolation,
|
||||
});
|
||||
|
||||
if (!thread) {
|
||||
throw new TRPCError({
|
||||
code: 'INTERNAL_SERVER_ERROR',
|
||||
message: 'Failed to create thread for task execution',
|
||||
});
|
||||
}
|
||||
|
||||
log('createClientTaskThread: created thread %s', thread.id);
|
||||
|
||||
// 2. Create initial user message (persisted to database)
|
||||
const userMessage = await ctx.messageModel.create({
|
||||
agentId,
|
||||
content: instruction,
|
||||
groupId,
|
||||
parentId: parentMessageId,
|
||||
role: 'user',
|
||||
threadId: thread.id,
|
||||
topicId,
|
||||
});
|
||||
|
||||
log('createClientTaskThread: created user message %s', userMessage.id);
|
||||
|
||||
// 3. Query thread messages and main chat messages in parallel
|
||||
const [threadMessages, messages] = await Promise.all([
|
||||
// Thread messages (messages within this thread)
|
||||
ctx.messageModel.query({ agentId, threadId: thread.id, topicId }),
|
||||
// Main chat messages (messages without threadId, includes updated taskDetail)
|
||||
// Pass both agentId and groupId - query() prioritizes groupId when present
|
||||
ctx.messageModel.query({ agentId, groupId, topicId }),
|
||||
]);
|
||||
|
||||
log(
|
||||
'createClientTaskThread: queried %d thread messages, %d main messages',
|
||||
threadMessages.length,
|
||||
messages.length,
|
||||
);
|
||||
|
||||
// 4. Return Thread, userMessageId, threadMessages and messages
|
||||
return {
|
||||
messages,
|
||||
startedAt,
|
||||
success: true,
|
||||
threadId: thread.id,
|
||||
threadMessages,
|
||||
userMessageId: userMessage.id,
|
||||
};
|
||||
} catch (error: any) {
|
||||
log('createClientTaskThread failed: %O', error);
|
||||
|
||||
if (error instanceof TRPCError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
throw new TRPCError({
|
||||
cause: error,
|
||||
code: 'INTERNAL_SERVER_ERROR',
|
||||
message: `Failed to create client task thread: ${error.message}`,
|
||||
});
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Create Thread for client-side task execution in Group mode
|
||||
*
|
||||
@@ -433,6 +346,94 @@ export const aiAgentRouter = router({
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Create Thread for client-side task execution
|
||||
*
|
||||
* This endpoint is called by desktop client when runInClient=true.
|
||||
* It creates the Thread but does NOT execute the task - execution happens on client side.
|
||||
*/
|
||||
createClientTaskThread: aiAgentProcedure
|
||||
.input(CreateClientTaskThreadSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { agentId, groupId, instruction, parentMessageId, title, topicId } = input;
|
||||
|
||||
log('createClientTaskThread: agentId=%s, groupId=%s', agentId, groupId);
|
||||
|
||||
try {
|
||||
// 1. Create Thread for isolated task execution
|
||||
const startedAt = new Date().toISOString();
|
||||
const thread = await ctx.threadModel.create({
|
||||
agentId,
|
||||
groupId,
|
||||
metadata: { clientMode: true, startedAt },
|
||||
sourceMessageId: parentMessageId,
|
||||
status: ThreadStatus.Processing,
|
||||
title,
|
||||
topicId,
|
||||
type: ThreadType.Isolation,
|
||||
});
|
||||
|
||||
if (!thread) {
|
||||
throw new TRPCError({
|
||||
code: 'INTERNAL_SERVER_ERROR',
|
||||
message: 'Failed to create thread for task execution',
|
||||
});
|
||||
}
|
||||
|
||||
log('createClientTaskThread: created thread %s', thread.id);
|
||||
|
||||
// 2. Create initial user message (persisted to database)
|
||||
const userMessage = await ctx.messageModel.create({
|
||||
agentId,
|
||||
content: instruction,
|
||||
groupId,
|
||||
parentId: parentMessageId,
|
||||
role: 'user',
|
||||
threadId: thread.id,
|
||||
topicId,
|
||||
});
|
||||
|
||||
log('createClientTaskThread: created user message %s', userMessage.id);
|
||||
|
||||
// 3. Query thread messages and main chat messages in parallel
|
||||
const [threadMessages, messages] = await Promise.all([
|
||||
// Thread messages (messages within this thread)
|
||||
ctx.messageModel.query({ agentId, threadId: thread.id, topicId }),
|
||||
// Main chat messages (messages without threadId, includes updated taskDetail)
|
||||
// Pass both agentId and groupId - query() prioritizes groupId when present
|
||||
ctx.messageModel.query({ agentId, groupId, topicId }),
|
||||
]);
|
||||
|
||||
log(
|
||||
'createClientTaskThread: queried %d thread messages, %d main messages',
|
||||
threadMessages.length,
|
||||
messages.length,
|
||||
);
|
||||
|
||||
// 4. Return Thread, userMessageId, threadMessages and messages
|
||||
return {
|
||||
messages,
|
||||
startedAt,
|
||||
success: true,
|
||||
threadId: thread.id,
|
||||
threadMessages,
|
||||
userMessageId: userMessage.id,
|
||||
};
|
||||
} catch (error: any) {
|
||||
log('createClientTaskThread failed: %O', error);
|
||||
|
||||
if (error instanceof TRPCError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
throw new TRPCError({
|
||||
cause: error,
|
||||
code: 'INTERNAL_SERVER_ERROR',
|
||||
message: `Failed to create client task thread: ${error.message}`,
|
||||
});
|
||||
}
|
||||
}),
|
||||
|
||||
createOperation: aiAgentProcedure
|
||||
.input(CreateAgentOperationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
@@ -907,6 +908,9 @@ export const aiAgentRouter = router({
|
||||
(a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(),
|
||||
);
|
||||
|
||||
// 6.1 Parse messages using conversation-flow for UI display
|
||||
const { flatList: parsedMessages } = parse(threadMessages);
|
||||
|
||||
// 7. Get result content when task is completed or failed
|
||||
let resultContent: string | undefined;
|
||||
if (updatedTaskStatus === 'completed' || updatedTaskStatus === 'failed') {
|
||||
@@ -974,6 +978,7 @@ export const aiAgentRouter = router({
|
||||
(updatedMetadata?.totalCost ? { total: updatedMetadata.totalCost } : undefined),
|
||||
currentActivity,
|
||||
error: updatedMetadata?.error ?? realtimeStatus?.currentState?.error,
|
||||
messages: parsedMessages,
|
||||
result: resultContent,
|
||||
status: updatedTaskStatus,
|
||||
stepCount: realtimeStatus?.currentState?.stepCount,
|
||||
|
||||
@@ -35,10 +35,6 @@ vi.mock('semver', async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('url-join', () => ({
|
||||
default: vi.fn((...args) => args.join('/')),
|
||||
}));
|
||||
|
||||
// 模拟 process.env
|
||||
const originalEnv = process.env;
|
||||
|
||||
@@ -282,33 +278,17 @@ describe('ChangelogService', () => {
|
||||
});
|
||||
|
||||
describe('replaceCdnUrl', () => {
|
||||
it('should replace URL with CDN URL if available', async () => {
|
||||
// 设置环境变量
|
||||
process.env.DOC_S3_PUBLIC_DOMAIN = 'https://cdn.example.com';
|
||||
|
||||
// 重新导入模块以确保环境变量生效
|
||||
const { ChangelogService } = await import('./index');
|
||||
const service = new ChangelogService();
|
||||
|
||||
service.cdnUrls = { 'https://example.com/image.jpg': 'image-hash.jpg' };
|
||||
|
||||
it('should replace /blog URL with CDN URL', () => {
|
||||
// @ts-ignore - accessing private method for testing
|
||||
const result = service.replaceCdnUrl('https://example.com/image.jpg');
|
||||
const result = service.replaceCdnUrl('/blog/image.jpg');
|
||||
|
||||
expect(result).toBe('https://cdn.example.com/image-hash.jpg');
|
||||
expect(result).toBe('https://hub-apac-1.lobeobjects.space/blog/image.jpg');
|
||||
});
|
||||
|
||||
it('should return original URL if CDN URL is not available', () => {
|
||||
const originalDocCdnPrefix = process.env.DOC_S3_PUBLIC_DOMAIN;
|
||||
process.env.DOC_S3_PUBLIC_DOMAIN = 'https://cdn.example.com';
|
||||
service.cdnUrls = {};
|
||||
|
||||
it('should return original URL if not starting with /blog', () => {
|
||||
// @ts-ignore - accessing private method for testing
|
||||
const result = service.replaceCdnUrl('https://example.com/image.jpg');
|
||||
expect(result).toBe('https://example.com/image.jpg');
|
||||
|
||||
// Restore original value
|
||||
process.env.DOC_S3_PUBLIC_DOMAIN = originalDocCdnPrefix;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user