Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/web/app/api/agent/projects/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,13 @@ export async function GET(request: NextRequest) {
archivedAt: true,
blockedByDependencies: {
select: {
dependencyTask: { select: { id: true, name: true, status: true } },
dependencyTask: { select: { id: true, name: true, status: true, archivedAt: true } },
},
orderBy: { createdAt: "asc" },
},
unblocksDependencies: {
select: {
blockedTask: { select: { id: true, name: true, status: true } },
blockedTask: { select: { id: true, name: true, status: true, archivedAt: true } },
},
orderBy: { createdAt: "asc" },
},
Expand Down
8 changes: 4 additions & 4 deletions apps/web/app/api/agent/tasks/[taskId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,13 @@ export async function GET(request: NextRequest, { params }: RouteContext) {
archivedAt: true,
blockedByDependencies: {
select: {
dependencyTask: { select: { id: true, name: true, status: true } },
dependencyTask: { select: { id: true, name: true, status: true, archivedAt: true } },
},
orderBy: { createdAt: "asc" },
},
unblocksDependencies: {
select: {
blockedTask: { select: { id: true, name: true, status: true } },
blockedTask: { select: { id: true, name: true, status: true, archivedAt: true } },
},
orderBy: { createdAt: "asc" },
},
Expand Down Expand Up @@ -389,13 +389,13 @@ export async function PATCH(request: NextRequest, { params }: RouteContext) {
archivedAt: true,
blockedByDependencies: {
select: {
dependencyTask: { select: { id: true, name: true, status: true } },
dependencyTask: { select: { id: true, name: true, status: true, archivedAt: true } },
},
orderBy: { createdAt: "asc" },
},
unblocksDependencies: {
select: {
blockedTask: { select: { id: true, name: true, status: true } },
blockedTask: { select: { id: true, name: true, status: true, archivedAt: true } },
},
orderBy: { createdAt: "asc" },
},
Expand Down
8 changes: 4 additions & 4 deletions apps/web/app/api/agent/tasks/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,13 @@ export async function GET(request: NextRequest) {
archivedAt: true,
blockedByDependencies: {
select: {
dependencyTask: { select: { id: true, name: true, status: true } },
dependencyTask: { select: { id: true, name: true, status: true, archivedAt: true } },
},
orderBy: { createdAt: "asc" },
},
unblocksDependencies: {
select: {
blockedTask: { select: { id: true, name: true, status: true } },
blockedTask: { select: { id: true, name: true, status: true, archivedAt: true } },
},
orderBy: { createdAt: "asc" },
},
Expand Down Expand Up @@ -346,13 +346,13 @@ export async function POST(request: NextRequest) {
archivedAt: true,
blockedByDependencies: {
select: {
dependencyTask: { select: { id: true, name: true, status: true } },
dependencyTask: { select: { id: true, name: true, status: true, archivedAt: true } },
},
orderBy: { createdAt: "asc" },
},
unblocksDependencies: {
select: {
blockedTask: { select: { id: true, name: true, status: true } },
blockedTask: { select: { id: true, name: true, status: true, archivedAt: true } },
},
orderBy: { createdAt: "asc" },
},
Expand Down
25 changes: 23 additions & 2 deletions apps/web/lib/api/task-dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ type DependencyTask = {
id: string
name: string
status: Status
archivedAt?: Date | null
}

type SerializedDependencyTask = Omit<DependencyTask, "archivedAt">

type DependencyEdge = {
dependencyTask: DependencyTask
}
Expand All @@ -21,8 +24,14 @@ export type TaskDependencyPayload = {

export function serializeTaskDependencies<T extends TaskDependencyPayload>(task: T) {
const { blockedByDependencies, unblocksDependencies, ...rest } = task
const dependencies = blockedByDependencies?.map((dependency) => dependency.dependencyTask) ?? []
const unblocks = unblocksDependencies?.map((dependency) => dependency.blockedTask) ?? []
const dependencies =
blockedByDependencies
?.map((dependency) => serializeDependencyTask(dependency.dependencyTask))
.filter((dependency): dependency is SerializedDependencyTask => dependency !== null) ?? []
const unblocks =
unblocksDependencies
?.map((dependency) => serializeDependencyTask(dependency.blockedTask))
.filter((dependency): dependency is SerializedDependencyTask => dependency !== null) ?? []

return {
...rest,
Expand All @@ -33,6 +42,18 @@ export function serializeTaskDependencies<T extends TaskDependencyPayload>(task:
}
}

function serializeDependencyTask(task: DependencyTask): SerializedDependencyTask | null {
if (task.archivedAt) {
return null
}

return {
id: task.id,
name: task.name,
status: task.status,
}
}

export function hasDependencyCycle(
edges: Array<{ blockedTaskId: string; dependencyTaskId: string }>,
blockedTaskId: string,
Expand Down
112 changes: 112 additions & 0 deletions apps/web/lib/api/task-visibility.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import assert from "node:assert/strict"
import { describe, it } from "node:test"

import { Status } from "@/generated/prisma/enums"

import { serializeTaskDependencies } from "./task-dependencies"

describe("Agent API task visibility", () => {
it("omits archived dependency and unblocks tasks from serialized task payloads", () => {
const activeDependency = {
id: "dependency-active",
name: "Active dependency",
status: Status.done,
archivedAt: null,
}
const archivedDependency = {
id: "dependency-archived",
name: "Archived dependency",
status: Status.done,
archivedAt: new Date("2026-05-16T07:00:00.000Z"),
}
const activeBlockedTask = {
id: "blocked-active",
name: "Active blocked task",
status: Status.todo,
archivedAt: null,
}
const archivedBlockedTask = {
id: "blocked-archived",
name: "Archived blocked task",
status: Status.todo,
archivedAt: new Date("2026-05-16T07:00:00.000Z"),
}

const result = serializeTaskDependencies({
id: "task-1",
blockedByDependencies: [
{ dependencyTask: activeDependency },
{ dependencyTask: archivedDependency },
],
unblocksDependencies: [
{ blockedTask: activeBlockedTask },
{ blockedTask: archivedBlockedTask },
],
})

assert.deepEqual(result.dependencies, [
{ id: activeDependency.id, name: activeDependency.name, status: activeDependency.status },
])
assert.deepEqual(result.dependencyIds, [activeDependency.id])
assert.deepEqual(result.unblocks, [
{ id: activeBlockedTask.id, name: activeBlockedTask.name, status: activeBlockedTask.status },
])
assert.equal(result.isDependencyReady, true)
})

it("omits archivedAt from serialized active dependency and unblocks tasks", () => {
const result = serializeTaskDependencies({
id: "task-1",
blockedByDependencies: [
{
dependencyTask: {
id: "dependency-active",
name: "Active dependency",
status: Status.done,
archivedAt: null,
},
},
],
unblocksDependencies: [
{
blockedTask: {
id: "blocked-active",
name: "Active blocked task",
status: Status.todo,
archivedAt: null,
},
},
],
})

assert.deepEqual(result.dependencies, [
{ id: "dependency-active", name: "Active dependency", status: Status.done },
])
assert.deepEqual(result.unblocks, [
{ id: "blocked-active", name: "Active blocked task", status: Status.todo },
])
assert.equal("archivedAt" in result.dependencies[0], false)
assert.equal("archivedAt" in result.unblocks[0], false)
})

it("does not mark dependency readiness from archived dependencies", () => {
const result = serializeTaskDependencies({
id: "task-1",
blockedByDependencies: [
{
dependencyTask: {
id: "dependency-archived",
name: "Archived dependency",
status: Status.done,
archivedAt: new Date("2026-05-16T07:00:00.000Z"),
},
},
],
unblocksDependencies: [],
})

assert.deepEqual(result.dependencies, [])
assert.deepEqual(result.dependencyIds, [])
assert.equal(result.isDependencyReady, false)
})
})
Loading