diff --git a/apps/server/trpc/routers/agent.ts b/apps/server/trpc/routers/agent.ts index a0c3660..b547b80 100644 --- a/apps/server/trpc/routers/agent.ts +++ b/apps/server/trpc/routers/agent.ts @@ -184,6 +184,17 @@ export function agentProcedures(publicProcedure: ProcedureBuilder) { return candidates[0] ?? null; }), + getTaskAgent: publicProcedure + .input(z.object({ taskId: z.string().min(1) })) + .query(async ({ ctx, input }): Promise => { + const agentManager = requireAgentManager(ctx); + const all = await agentManager.list(); + const matches = all + .filter(a => a.taskId === input.taskId) + .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); + return matches[0] ?? null; + }), + getActiveConflictAgent: publicProcedure .input(z.object({ initiativeId: z.string().min(1) })) .query(async ({ ctx, input }): Promise => { diff --git a/apps/web/src/components/execution/TaskSlideOver.tsx b/apps/web/src/components/execution/TaskSlideOver.tsx index ff9a4d3..0885782 100644 --- a/apps/web/src/components/execution/TaskSlideOver.tsx +++ b/apps/web/src/components/execution/TaskSlideOver.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useRef, useMemo } from "react"; +import { useCallback, useEffect, useRef, useMemo, useState } from "react"; import { motion, AnimatePresence } from "motion/react"; import { X, Trash2, MessageCircle, RotateCw } from "lucide-react"; import type { ChatTarget } from "@/components/chat/ChatSlideOver"; @@ -7,12 +7,15 @@ import { Button } from "@/components/ui/button"; import { StatusBadge } from "@/components/StatusBadge"; import { StatusDot } from "@/components/StatusDot"; import { TiptapEditor } from "@/components/editor/TiptapEditor"; +import { AgentOutputViewer } from "@/components/AgentOutputViewer"; import { getCategoryConfig } from "@/lib/category"; import { markdownToTiptapJson } from "@/lib/markdown-to-tiptap"; import { useExecutionContext } from "./ExecutionContext"; import { trpc } from "@/lib/trpc"; import { cn } from "@/lib/utils"; +type SlideOverTab = "details" | "logs"; + interface TaskSlideOverProps { onOpenChat?: (target: ChatTarget) => void; } @@ -24,8 +27,15 @@ export function TaskSlideOver({ onOpenChat }: TaskSlideOverProps) { const deleteTaskMutation = trpc.deleteTask.useMutation(); const updateTaskMutation = trpc.updateTask.useMutation(); + const [tab, setTab] = useState("details"); + const close = useCallback(() => setSelectedTaskId(null), [setSelectedTaskId]); + // Reset tab when task changes + useEffect(() => { + setTab("details"); + }, [selectedEntry?.task?.id]); + // Escape key closes useEffect(() => { if (!selectedEntry) return; @@ -152,80 +162,107 @@ export function TaskSlideOver({ onOpenChat }: TaskSlideOverProps) { + {/* Tab bar */} +
+ {(["details", "logs"] as const).map((t) => ( + + ))} +
+ {/* Content */} -
- {/* Metadata grid */} -
- - - - - - - - - - - {task.type} - - - - {selectedEntry.agentName ?? "Unassigned"} - - -
+
+ {tab === "details" ? ( +
+ {/* Metadata grid */} +
+ + + + + + + + + + + {task.type} + + + + {selectedEntry.agentName ?? "Unassigned"} + + +
- {/* Description — editable tiptap */} -
- -
+ {/* Description — editable tiptap */} +
+ +
- {/* Dependencies */} -
- {dependencies.length === 0 ? ( -

None

- ) : ( -
    - {dependencies.map((dep) => ( -
  • - - - {dep.name} - -
  • - ))} -
- )} -
+ {/* Dependencies */} +
+ {dependencies.length === 0 ? ( +

None

+ ) : ( +
    + {dependencies.map((dep) => ( +
  • + + + {dep.name} + +
  • + ))} +
+ )} +
- {/* Blocks */} -
- {dependents.length === 0 ? ( -

None

- ) : ( -
    - {dependents.map((dep) => ( -
  • - - - {dep.name} - -
  • - ))} -
- )} -
+ {/* Blocks */} +
+ {dependents.length === 0 ? ( +

None

+ ) : ( +
    + {dependents.map((dep) => ( +
  • + + + {dep.name} + +
  • + ))} +
+ )} +
+
+ ) : ( + + )}
{/* Footer */} @@ -293,6 +330,43 @@ export function TaskSlideOver({ onOpenChat }: TaskSlideOverProps) { ); } +// --------------------------------------------------------------------------- +// Agent Logs Tab +// --------------------------------------------------------------------------- + +function AgentLogsTab({ taskId }: { taskId: string }) { + const { data: agent, isLoading } = trpc.getTaskAgent.useQuery( + { taskId }, + { refetchOnWindowFocus: false }, + ); + + if (isLoading) { + return ( +
+ Loading... +
+ ); + } + + if (!agent) { + return ( +
+ No agent has been assigned to this task yet. +
+ ); + } + + return ( +
+ +
+ ); +} + // --------------------------------------------------------------------------- // Small helpers // ---------------------------------------------------------------------------