diff --git a/src/trpc/router.ts b/src/trpc/router.ts index 127c097..f1d8b67 100644 --- a/src/trpc/router.ts +++ b/src/trpc/router.ts @@ -14,7 +14,7 @@ import type { MessageRepository } from '../db/repositories/message-repository.js import type { InitiativeRepository } from '../db/repositories/initiative-repository.js'; import type { PhaseRepository } from '../db/repositories/phase-repository.js'; import type { PlanRepository } from '../db/repositories/plan-repository.js'; -import type { DispatchManager } from '../dispatch/types.js'; +import type { DispatchManager, PhaseDispatchManager } from '../dispatch/types.js'; import type { CoordinationManager } from '../coordination/types.js'; import type { Phase, Plan, Task } from '../db/schema.js'; import { buildDiscussPrompt, buildBreakdownPrompt, buildDecomposePrompt } from '../agent/prompts.js'; @@ -278,6 +278,19 @@ function requirePlanRepository(ctx: TRPCContext): PlanRepository { return ctx.planRepository; } +/** + * Helper to ensure phaseDispatchManager is available in context. + */ +function requirePhaseDispatchManager(ctx: TRPCContext): PhaseDispatchManager { + if (!ctx.phaseDispatchManager) { + throw new TRPCError({ + code: 'INTERNAL_SERVER_ERROR', + message: 'Phase dispatch manager not available', + }); + } + return ctx.phaseDispatchManager; +} + // ============================================================================= // Application Router with Procedures // ============================================================================= @@ -855,6 +868,87 @@ export const appRouter = router({ return created; }), + /** + * Create a dependency between two phases. + * The phase with phaseId depends on the phase with dependsOnPhaseId. + */ + createPhaseDependency: publicProcedure + .input(z.object({ + phaseId: z.string().min(1), + dependsOnPhaseId: z.string().min(1), + })) + .mutation(async ({ ctx, input }) => { + const repo = requirePhaseRepository(ctx); + + // Validate both phases exist + const phase = await repo.findById(input.phaseId); + if (!phase) { + throw new TRPCError({ + code: 'NOT_FOUND', + message: `Phase '${input.phaseId}' not found`, + }); + } + + const dependsOnPhase = await repo.findById(input.dependsOnPhaseId); + if (!dependsOnPhase) { + throw new TRPCError({ + code: 'NOT_FOUND', + message: `Phase '${input.dependsOnPhaseId}' not found`, + }); + } + + await repo.createDependency(input.phaseId, input.dependsOnPhaseId); + return { success: true }; + }), + + /** + * Get dependencies for a phase. + * Returns IDs of phases that this phase depends on. + */ + getPhaseDependencies: publicProcedure + .input(z.object({ phaseId: z.string().min(1) })) + .query(async ({ ctx, input }) => { + const repo = requirePhaseRepository(ctx); + const dependencies = await repo.getDependencies(input.phaseId); + return { dependencies }; + }), + + // =========================================================================== + // Phase Dispatch Procedures + // =========================================================================== + + /** + * Queue a phase for dispatch. + * Phase will be dispatched when all dependencies complete. + */ + queuePhase: publicProcedure + .input(z.object({ phaseId: z.string().min(1) })) + .mutation(async ({ ctx, input }) => { + const phaseDispatchManager = requirePhaseDispatchManager(ctx); + await phaseDispatchManager.queuePhase(input.phaseId); + return { success: true }; + }), + + /** + * Dispatch next available phase. + * Returns dispatch result with phase info. + */ + dispatchNextPhase: publicProcedure + .mutation(async ({ ctx }) => { + const phaseDispatchManager = requirePhaseDispatchManager(ctx); + return phaseDispatchManager.dispatchNextPhase(); + }), + + /** + * Get current phase queue state. + * Returns queued, ready, and blocked phase counts. + */ + getPhaseQueueState: publicProcedure + .query(async ({ ctx }) => { + const phaseDispatchManager = requirePhaseDispatchManager(ctx); + return phaseDispatchManager.getPhaseQueueState(); + }), + // =========================================================================== // Plan Procedures // ===========================================================================