mirror of
https://github.com/lobehub/lobehub.git
synced 2026-03-26 13:19:34 +07:00
♻️ refactor: update model/service/router for new task schema
- Add TaskTopicHandoff type to @lobechat/types - Update taskTopic model to use single handoff jsonb field - Update task service and router to read handoff as typed object - Update addComment callers with authorUserId/authorAgentId - Sync all tests with schema changes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -667,8 +667,18 @@ describe('TaskModel', () => {
|
||||
const model = new TaskModel(serverDB, userId);
|
||||
const task = await model.create({ instruction: 'Test' });
|
||||
|
||||
await model.addComment({ content: 'First comment', taskId: task.id, userId });
|
||||
await model.addComment({ content: 'Second comment', taskId: task.id, userId });
|
||||
await model.addComment({
|
||||
authorUserId: userId,
|
||||
content: 'First comment',
|
||||
taskId: task.id,
|
||||
userId,
|
||||
});
|
||||
await model.addComment({
|
||||
authorUserId: userId,
|
||||
content: 'Second comment',
|
||||
taskId: task.id,
|
||||
userId,
|
||||
});
|
||||
|
||||
const comments = await model.getComments(task.id);
|
||||
expect(comments).toHaveLength(2);
|
||||
@@ -681,6 +691,7 @@ describe('TaskModel', () => {
|
||||
const task = await model.create({ instruction: 'Test' });
|
||||
|
||||
const comment = await model.addComment({
|
||||
authorUserId: userId,
|
||||
briefId: '00000000-0000-0000-0000-000000000001',
|
||||
content: 'Reply to brief',
|
||||
taskId: task.id,
|
||||
@@ -697,13 +708,14 @@ describe('TaskModel', () => {
|
||||
const task = await model.create({ instruction: 'Test' });
|
||||
|
||||
const comment = await model.addComment({
|
||||
agentId: 'agt_xxx',
|
||||
authorAgentId: 'agt_xxx',
|
||||
content: 'Agent observation',
|
||||
taskId: task.id,
|
||||
userId,
|
||||
});
|
||||
|
||||
expect(comment.agentId).toBe('agt_xxx');
|
||||
expect(comment.userId).toBeNull();
|
||||
expect(comment.authorAgentId).toBe('agt_xxx');
|
||||
expect(comment.authorUserId).toBeNull();
|
||||
});
|
||||
|
||||
it('should delete own comment', async () => {
|
||||
@@ -711,6 +723,7 @@ describe('TaskModel', () => {
|
||||
const task = await model.create({ instruction: 'Test' });
|
||||
|
||||
const comment = await model.addComment({
|
||||
authorUserId: userId,
|
||||
content: 'To be deleted',
|
||||
taskId: task.id,
|
||||
userId,
|
||||
@@ -729,6 +742,7 @@ describe('TaskModel', () => {
|
||||
const task = await model1.create({ instruction: 'Test' });
|
||||
|
||||
const comment = await model1.addComment({
|
||||
authorUserId: userId,
|
||||
content: 'User 1 comment',
|
||||
taskId: task.id,
|
||||
userId,
|
||||
@@ -742,9 +756,9 @@ describe('TaskModel', () => {
|
||||
const model = new TaskModel(serverDB, userId);
|
||||
const task = await model.create({ instruction: 'Test' });
|
||||
|
||||
await model.addComment({ content: 'First', taskId: task.id, userId });
|
||||
await model.addComment({ content: 'Second', taskId: task.id, userId });
|
||||
await model.addComment({ content: 'Third', taskId: task.id, userId });
|
||||
await model.addComment({ authorUserId: userId, content: 'First', taskId: task.id, userId });
|
||||
await model.addComment({ authorUserId: userId, content: 'Second', taskId: task.id, userId });
|
||||
await model.addComment({ authorUserId: userId, content: 'Third', taskId: task.id, userId });
|
||||
|
||||
const comments = await model.getComments(task.id);
|
||||
expect(comments).toHaveLength(3);
|
||||
|
||||
@@ -102,10 +102,11 @@ describe('TaskTopicModel', () => {
|
||||
});
|
||||
|
||||
const topics = await topicModel.findByTaskId(task.id);
|
||||
expect(topics[0].handoffTitle).toBe('第1章完成');
|
||||
expect(topics[0].handoffSummary).toBe('Completed chapter 1');
|
||||
expect(topics[0].handoffNextAction).toBe('Continue writing');
|
||||
expect(topics[0].handoffKeyFindings).toEqual(['Finding 1', 'Finding 2']);
|
||||
const handoff = topics[0].handoff as any;
|
||||
expect(handoff.title).toBe('第1章完成');
|
||||
expect(handoff.summary).toBe('Completed chapter 1');
|
||||
expect(handoff.nextAction).toBe('Continue writing');
|
||||
expect(handoff.keyFindings).toEqual(['Finding 1', 'Finding 2']);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { TaskTopicHandoff } from '@lobechat/types';
|
||||
import { and, desc, eq, sql } from 'drizzle-orm';
|
||||
|
||||
import type { TaskTopicItem } from '../schemas/task';
|
||||
@@ -43,24 +44,10 @@ export class TaskTopicModel {
|
||||
);
|
||||
}
|
||||
|
||||
async updateHandoff(
|
||||
taskId: string,
|
||||
topicId: string,
|
||||
handoff: {
|
||||
keyFindings?: string[];
|
||||
nextAction?: string;
|
||||
summary?: string;
|
||||
title?: string;
|
||||
},
|
||||
): Promise<void> {
|
||||
async updateHandoff(taskId: string, topicId: string, handoff: TaskTopicHandoff): Promise<void> {
|
||||
await this.db
|
||||
.update(taskTopics)
|
||||
.set({
|
||||
handoffKeyFindings: handoff.keyFindings,
|
||||
handoffNextAction: handoff.nextAction,
|
||||
handoffSummary: handoff.summary,
|
||||
handoffTitle: handoff.title,
|
||||
})
|
||||
.set({ handoff })
|
||||
.where(
|
||||
and(
|
||||
eq(taskTopics.taskId, taskId),
|
||||
@@ -135,10 +122,7 @@ export class TaskTopicModel {
|
||||
return this.db
|
||||
.select({
|
||||
createdAt: topics.createdAt,
|
||||
handoffKeyFindings: taskTopics.handoffKeyFindings,
|
||||
handoffNextAction: taskTopics.handoffNextAction,
|
||||
handoffSummary: taskTopics.handoffSummary,
|
||||
handoffTitle: taskTopics.handoffTitle,
|
||||
handoff: taskTopics.handoff,
|
||||
id: topics.id,
|
||||
metadata: topics.metadata,
|
||||
operationId: taskTopics.operationId,
|
||||
@@ -162,10 +146,7 @@ export class TaskTopicModel {
|
||||
return this.db
|
||||
.select({
|
||||
createdAt: taskTopics.createdAt,
|
||||
handoffKeyFindings: taskTopics.handoffKeyFindings,
|
||||
handoffNextAction: taskTopics.handoffNextAction,
|
||||
handoffSummary: taskTopics.handoffSummary,
|
||||
handoffTitle: taskTopics.handoffTitle,
|
||||
handoff: taskTopics.handoff,
|
||||
seq: taskTopics.seq,
|
||||
status: taskTopics.status,
|
||||
topicId: taskTopics.topicId,
|
||||
|
||||
@@ -31,6 +31,13 @@ export interface WorkspaceData {
|
||||
tree: WorkspaceTreeNode[];
|
||||
}
|
||||
|
||||
export interface TaskTopicHandoff {
|
||||
keyFindings?: string[];
|
||||
nextAction?: string;
|
||||
summary?: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
// ── Task Detail (shared across CLI, viewTask tool, task.detail router) ──
|
||||
|
||||
export interface TaskDetailSubtask {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { TaskIdentifier as TaskSkillIdentifier } from '@lobechat/builtin-skills'
|
||||
import { BriefIdentifier } from '@lobechat/builtin-tool-brief';
|
||||
import { NotebookIdentifier } from '@lobechat/builtin-tool-notebook';
|
||||
import { buildTaskRunPrompt } from '@lobechat/prompts';
|
||||
import type { WorkspaceData } from '@lobechat/types';
|
||||
import type { TaskTopicHandoff, WorkspaceData } from '@lobechat/types';
|
||||
import { TRPCError } from '@trpc/server';
|
||||
import { z } from 'zod';
|
||||
|
||||
@@ -175,7 +175,7 @@ async function buildTaskPrompt(
|
||||
type: b.type,
|
||||
})),
|
||||
comments: comments.map((c: any) => ({
|
||||
agentId: c.agentId,
|
||||
agentId: c.authorAgentId,
|
||||
content: c.content,
|
||||
createdAt: c.createdAt,
|
||||
id: c.id,
|
||||
@@ -187,22 +187,17 @@ async function buildTaskPrompt(
|
||||
name: s.name,
|
||||
status: s.status,
|
||||
})),
|
||||
topics: (topics as any[]).map((t) => ({
|
||||
createdAt: t.createdAt,
|
||||
handoff:
|
||||
t.handoffSummary || t.handoffTitle
|
||||
? {
|
||||
keyFindings: t.handoffKeyFindings as string[] | undefined,
|
||||
nextAction: t.handoffNextAction as string | undefined,
|
||||
summary: t.handoffSummary as string | undefined,
|
||||
title: t.handoffTitle as string | undefined,
|
||||
}
|
||||
: null,
|
||||
id: t.topicId || t.id,
|
||||
seq: t.seq,
|
||||
status: t.status,
|
||||
title: t.handoffTitle || t.title,
|
||||
})),
|
||||
topics: (topics as any[]).map((t) => {
|
||||
const handoff = t.handoff as TaskTopicHandoff | null;
|
||||
return {
|
||||
createdAt: t.createdAt,
|
||||
handoff,
|
||||
id: t.topicId || t.id,
|
||||
seq: t.seq,
|
||||
status: t.status,
|
||||
title: handoff?.title || t.title,
|
||||
};
|
||||
}),
|
||||
},
|
||||
extraPrompt,
|
||||
parentTask: parentTaskContext,
|
||||
@@ -325,6 +320,7 @@ export const taskRouter = router({
|
||||
const model = ctx.taskModel;
|
||||
const task = await resolveOrThrow(model, input.id);
|
||||
const comment = await model.addComment({
|
||||
authorUserId: ctx.userId,
|
||||
briefId: input.briefId,
|
||||
content: input.content,
|
||||
taskId: task.id,
|
||||
|
||||
@@ -2,6 +2,7 @@ import type {
|
||||
TaskDetailActivity,
|
||||
TaskDetailData,
|
||||
TaskDetailWorkspaceNode,
|
||||
TaskTopicHandoff,
|
||||
WorkspaceData,
|
||||
} from '@lobechat/types';
|
||||
|
||||
@@ -92,7 +93,7 @@ export class TaskService {
|
||||
seq: t.seq,
|
||||
status: t.status,
|
||||
time: toISO(t.createdAt),
|
||||
title: t.handoffTitle || 'Untitled',
|
||||
title: (t.handoff as TaskTopicHandoff | null)?.title || 'Untitled',
|
||||
type: 'topic' as const,
|
||||
})),
|
||||
...briefs.map((b) => ({
|
||||
@@ -110,7 +111,7 @@ export class TaskService {
|
||||
type: 'brief' as const,
|
||||
})),
|
||||
...comments.map((c) => ({
|
||||
agentId: c.agentId,
|
||||
agentId: c.authorAgentId,
|
||||
content: c.content,
|
||||
time: toISO(c.createdAt),
|
||||
type: 'comment' as const,
|
||||
|
||||
Reference in New Issue
Block a user