feat(05-02): add task tRPC procedures

- Add TaskRepository to TRPCContext (optional, same pattern as AgentManager)
- Add requireTaskRepository helper function
- Add listTasks procedure (query tasks by planId, ordered by order field)
- Add getTask procedure (get single task by ID, throws NOT_FOUND)
- Add updateTaskStatus procedure (update task status to pending/in_progress/completed/blocked)
This commit is contained in:
Lukas May
2026-01-30 20:33:26 +01:00
parent b2e7c2920f
commit 9f24c1ffc0
2 changed files with 77 additions and 0 deletions

View File

@@ -7,6 +7,7 @@
import type { EventBus, DomainEvent } from '../events/types.js'; import type { EventBus, DomainEvent } from '../events/types.js';
import type { AgentManager } from '../agent/types.js'; import type { AgentManager } from '../agent/types.js';
import type { TaskRepository } from '../db/repositories/task-repository.js';
// Re-export for convenience // Re-export for convenience
export type { EventBus, DomainEvent }; export type { EventBus, DomainEvent };
@@ -23,6 +24,8 @@ export interface TRPCContext {
processCount: number; processCount: number;
/** Agent manager for agent lifecycle operations (optional until server wiring complete) */ /** Agent manager for agent lifecycle operations (optional until server wiring complete) */
agentManager?: AgentManager; agentManager?: AgentManager;
/** Task repository for task CRUD operations (optional until server wiring complete) */
taskRepository?: TaskRepository;
} }
/** /**
@@ -33,6 +36,7 @@ export interface CreateContextOptions {
serverStartedAt: Date | null; serverStartedAt: Date | null;
processCount: number; processCount: number;
agentManager?: AgentManager; agentManager?: AgentManager;
taskRepository?: TaskRepository;
} }
/** /**
@@ -47,5 +51,6 @@ export function createContext(options: CreateContextOptions): TRPCContext {
serverStartedAt: options.serverStartedAt, serverStartedAt: options.serverStartedAt,
processCount: options.processCount, processCount: options.processCount,
agentManager: options.agentManager, agentManager: options.agentManager,
taskRepository: options.taskRepository,
}; };
} }

View File

@@ -9,6 +9,7 @@ import { initTRPC, TRPCError } from '@trpc/server';
import { z } from 'zod'; import { z } from 'zod';
import type { TRPCContext } from './context.js'; import type { TRPCContext } from './context.js';
import type { AgentInfo, AgentResult } from '../agent/types.js'; import type { AgentInfo, AgentResult } from '../agent/types.js';
import type { TaskRepository } from '../db/repositories/task-repository.js';
/** /**
* Initialize tRPC with our context type. * Initialize tRPC with our context type.
@@ -176,6 +177,19 @@ function requireAgentManager(ctx: TRPCContext) {
return ctx.agentManager; return ctx.agentManager;
} }
/**
* Helper to ensure taskRepository is available in context.
*/
function requireTaskRepository(ctx: TRPCContext): TaskRepository {
if (!ctx.taskRepository) {
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'Task repository not available',
});
}
return ctx.taskRepository;
}
// ============================================================================= // =============================================================================
// Application Router with Procedures // Application Router with Procedures
// ============================================================================= // =============================================================================
@@ -193,6 +207,9 @@ function requireAgentManager(ctx: TRPCContext) {
* - getAgentByName: Get agent by name * - getAgentByName: Get agent by name
* - resumeAgent: Resume an agent waiting for input * - resumeAgent: Resume an agent waiting for input
* - getAgentResult: Get result of agent's work * - getAgentResult: Get result of agent's work
* - listTasks: List tasks for a plan
* - getTask: Get task by ID
* - updateTaskStatus: Update task status
*/ */
export const appRouter = router({ export const appRouter = router({
/** /**
@@ -323,6 +340,61 @@ export const appRouter = router({
const agent = await resolveAgent(ctx, input); const agent = await resolveAgent(ctx, input);
return agentManager.getResult(agent.id); return agentManager.getResult(agent.id);
}), }),
// ===========================================================================
// Task Procedures
// ===========================================================================
/**
* List tasks for a plan.
* Returns tasks ordered by order field.
*/
listTasks: publicProcedure
.input(z.object({ planId: z.string().min(1) }))
.query(async ({ ctx, input }) => {
const taskRepository = requireTaskRepository(ctx);
return taskRepository.findByPlanId(input.planId);
}),
/**
* Get a task by ID.
* Throws NOT_FOUND if task doesn't exist.
*/
getTask: publicProcedure
.input(z.object({ id: z.string().min(1) }))
.query(async ({ ctx, input }) => {
const taskRepository = requireTaskRepository(ctx);
const task = await taskRepository.findById(input.id);
if (!task) {
throw new TRPCError({
code: 'NOT_FOUND',
message: `Task '${input.id}' not found`,
});
}
return task;
}),
/**
* Update a task's status.
* Returns the updated task.
*/
updateTaskStatus: publicProcedure
.input(z.object({
id: z.string().min(1),
status: z.enum(['pending', 'in_progress', 'completed', 'blocked']),
}))
.mutation(async ({ ctx, input }) => {
const taskRepository = requireTaskRepository(ctx);
// Check task exists first
const existing = await taskRepository.findById(input.id);
if (!existing) {
throw new TRPCError({
code: 'NOT_FOUND',
message: `Task '${input.id}' not found`,
});
}
return taskRepository.update(input.id, { status: input.status });
}),
}); });
/** /**