♻️ 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:
arvinxx
2026-03-25 15:52:52 +08:00
parent 63f0f72c81
commit 9d15148033
6 changed files with 56 additions and 56 deletions

View File

@@ -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);

View File

@@ -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']);
});
});

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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,