* 创建任务 * - 校验项目存在 * - 自动计算 position(同状态下最大 position + 1)
(projectId: string, input: CreateTaskInput)
| 401 | latestAgentType: preferredWorkspace.agentType as import('@agent-tower/shared').AgentType, |
| 402 | } : {}), |
| 403 | ...(hasRunningSession ? { hasRunningSession: true as const } : {}), |
| 404 | updatedAt: task.updatedAt.getTime(), |
| 405 | }; |
| 406 | }); |
| 407 | |
| 408 | return { |
| 409 | data, |
| 410 | total, |
| 411 | page, |
| 412 | limit, |
| 413 | totalPages: Math.ceil(total / limit), |
| 414 | }; |
| 415 | } |
| 416 | |
| 417 | /** |
| 418 | * 获取项目的任务列表(支持按状态过滤和分页) |
| 419 | */ |
| 420 | async findByProjectId(projectId: string, params: FindTasksParams = {}) { |
| 421 | // 校验项目存在 |
| 422 | const project = await prisma.project.findUnique({ |
| 423 | where: { id: projectId }, |
| 424 | }); |
| 425 | if (!project) { |
| 426 | throw new NotFoundError('Project', projectId); |
| 427 | } |
| 428 | |
| 429 | const page = Math.max(1, params.page || 1); |
| 430 | const limit = Math.min(1000, Math.max(1, params.limit || 200)); |
| 431 | const skip = (page - 1) * limit; |
| 432 | |
| 433 | const where: any = { projectId, deletedAt: null }; |
| 434 | if (params.status) { |
| 435 | where.status = params.status; |
| 436 | } |
| 437 | |
| 438 | // Active tasks first (IN_PROGRESS → IN_REVIEW → TODO), then completed (DONE → CANCELLED). |
| 439 | // Raw SQL CASE needed because Prisma orderBy doesn't support custom sort functions. |
| 440 | const statusOrder: Record<string, number> = { |
| 441 | [TaskStatus.IN_PROGRESS]: 0, |