From a3f824fb2fe4d36d84f12c4e5009cc78f509c7f3 Mon Sep 17 00:00:00 2001 From: Kaito Date: Sat, 16 May 2026 19:45:51 +0700 Subject: [PATCH] fix(api): persist agent task read markers --- .../web/app/api/agent/tasks/[taskId]/route.ts | 28 ++++++---------- apps/web/app/api/agent/tasks/route.ts | 32 +++++++++++++++---- apps/web/lib/api/task-read-marker-writes.ts | 21 ++++++++++++ apps/web/lib/api/task-read-markers.test.ts | 15 +++++++++ 4 files changed, 70 insertions(+), 26 deletions(-) create mode 100644 apps/web/lib/api/task-read-marker-writes.ts diff --git a/apps/web/app/api/agent/tasks/[taskId]/route.ts b/apps/web/app/api/agent/tasks/[taskId]/route.ts index 6d7043a..98a861c 100644 --- a/apps/web/app/api/agent/tasks/[taskId]/route.ts +++ b/apps/web/app/api/agent/tasks/[taskId]/route.ts @@ -3,6 +3,7 @@ import { NextRequest, NextResponse } from "next/server" import { agentAuth } from "@/lib/agent-auth" import { createAuditLog, formatChangedFields } from "@/lib/api/audit-log" import { validateAgentTaskPatchInput } from "@/lib/api/agent-task-validation" +import { createTaskReadMarkerRows } from "@/lib/api/task-read-marker-writes" import { getTaskFreshnessUpdate } from "@/lib/api/task-freshness" import { serializeTaskReadMarkers } from "@/lib/api/task-read-markers" import { agentTaskUpdater } from "@/lib/api/task-updater" @@ -336,25 +337,14 @@ export async function PATCH(request: NextRequest, { params }: RouteContext) { }, }) - await Promise.all( - readAgents.map((readAgent) => - tx.taskReadMarker.upsert({ - where: { - taskId_agentId_status: { - taskId: task.id, - agentId: readAgent.id, - status: nextStatus, - }, - }, - create: { - taskId: task.id, - agentId: readAgent.id, - status: nextStatus, - }, - update: { readAt: new Date() }, - }) - ) - ) + await tx.taskReadMarker.createMany({ + data: createTaskReadMarkerRows({ + taskId: task.id, + readAgents, + status: nextStatus, + }), + skipDuplicates: true, + }) } if (shouldClearNextStatusReads) { diff --git a/apps/web/app/api/agent/tasks/route.ts b/apps/web/app/api/agent/tasks/route.ts index 6f0bf72..e77d9c1 100644 --- a/apps/web/app/api/agent/tasks/route.ts +++ b/apps/web/app/api/agent/tasks/route.ts @@ -3,6 +3,7 @@ import { NextRequest, NextResponse } from "next/server" import { Status } from "@/generated/prisma/enums" import { agentAuth } from "@/lib/agent-auth" import { createAuditLog } from "@/lib/api/audit-log" +import { createTaskReadMarkerRows } from "@/lib/api/task-read-marker-writes" import { serializeTaskReadMarkers } from "@/lib/api/task-read-markers" import { agentTaskUpdater } from "@/lib/api/task-updater" import { prisma } from "@/lib/prisma" @@ -325,7 +326,7 @@ export async function POST(request: NextRequest) { skipDuplicates: true, }) - return tx.task.create({ + const createdTask = await tx.task.create({ data: { projectId, assignedAgentId, @@ -336,13 +337,23 @@ export async function POST(request: NextRequest) { summaryUpdatedAt: note ? new Date() : null, blockingReason: blockingReason || null, ...agentTaskUpdater(agent), - readMarkers: { - create: project.company.agents.map((readAgent) => ({ - agentId: readAgent.id, - status: status as Status, - })), - }, }, + select: { id: true }, + }) + + if (project.company.agents.length) { + await tx.taskReadMarker.createMany({ + data: createTaskReadMarkerRows({ + taskId: createdTask.id, + readAgents: project.company.agents, + status: status as Status, + }), + skipDuplicates: true, + }) + } + + return tx.task.findUniqueOrThrow({ + where: { id: createdTask.id }, select: { id: true, name: true, @@ -364,6 +375,13 @@ export async function POST(request: NextRequest) { }, orderBy: { createdAt: "asc" }, }, + project: { + select: { + id: true, + name: true, + description: true, + }, + }, taskUpdatedAt: true, taskUpdatedById: true, taskUpdatedByName: true, diff --git a/apps/web/lib/api/task-read-marker-writes.ts b/apps/web/lib/api/task-read-marker-writes.ts new file mode 100644 index 0000000..30783bf --- /dev/null +++ b/apps/web/lib/api/task-read-marker-writes.ts @@ -0,0 +1,21 @@ +import { Status } from "@/generated/prisma/enums" + +type ReadMarkerAgent = { + id: string +} + +export function createTaskReadMarkerRows({ + taskId, + readAgents, + status, +}: { + taskId: string + readAgents: ReadMarkerAgent[] + status: Status +}) { + return readAgents.map((readAgent) => ({ + taskId, + agentId: readAgent.id, + status, + })) +} diff --git a/apps/web/lib/api/task-read-markers.test.ts b/apps/web/lib/api/task-read-markers.test.ts index e9f81ae..b8c4fcd 100644 --- a/apps/web/lib/api/task-read-markers.test.ts +++ b/apps/web/lib/api/task-read-markers.test.ts @@ -2,6 +2,7 @@ import assert from "node:assert/strict" import { describe, it } from "node:test" import { Status } from "@/generated/prisma/enums" +import { createTaskReadMarkerRows } from "./task-read-marker-writes" import { getStoredSummaryUpdatedAt, serializeTaskReadMarkers } from "./task-read-markers" const markerAgent = { AgentId: "main" } @@ -41,4 +42,18 @@ describe("Agent API task serialization", () => { null ) }) + + it("builds task/status-scoped read marker rows for explicit readBy writes", () => { + assert.deepEqual( + createTaskReadMarkerRows({ + taskId: "task-1", + readAgents: [{ id: "agent-main" }, { id: "agent-kaito" }], + status: Status.done, + }), + [ + { taskId: "task-1", agentId: "agent-main", status: Status.done }, + { taskId: "task-1", agentId: "agent-kaito", status: Status.done }, + ] + ) + }) })