import { useMemo } from 'react' import type { KanbanTask, Session } from '../types/kanban' import type { ChatMessage } from '../types/chat' import { AGENT_STATUS_LABEL, PRIORITY_META } from '../types/kanban' import type { AgentInfo } from '../types/visual' import { MarkdownBody, MessageList } from '../chat/MessageList' import { getLinkedRuntimeTaskId } from '../lib/workItemRuntimeIds' interface TaskDetailViewProps { task: KanbanTask linkedSession?: Session | null linkedSessionMessages?: ChatMessage[] agents: AgentInfo[] onBack: () => void onOpenLinkedSession?: (taskId: string) => void onOpenExecutionPanel?: (taskId: string) => void } function prettyJson(value: unknown): string { try { return JSON.stringify(value, null, 2) } catch { return String(value ?? '') } } function stringList(value: string[] | undefined): string[] { return (value ?? []).map(item => item.trim()).filter(Boolean) } function infoPairsFromRecord(value: Record | undefined): Array<[string, string]> { if (!value) return [] return Object.entries(value) .filter(([, entry]) => entry !== null && entry !== undefined && entry !== '' && entry !== false) .map(([key, entry]) => { if (Array.isArray(entry)) return [key, entry.join(', ')] if (typeof entry === 'object') return [key, prettyJson(entry)] return [key, String(entry)] }) } export function TaskDetailView({ task, linkedSession, linkedSessionMessages, agents, onBack, onOpenLinkedSession, onOpenExecutionPanel, }: TaskDetailViewProps) { const liveAssignees = useMemo(() => ( task.assigneeIds .map(id => agents.find(agent => agent.agent_id === id)) .filter(Boolean) as AgentInfo[] ), [agents, task.assigneeIds]) const priorityMeta = task.priority ? PRIORITY_META[task.priority] : null const residentAssignment = (task.residentAssignment ?? {}) as Record const memberSessionState = (task.memberSessionState ?? {}) as Record const ownershipContract = (task.ownershipContract ?? {}) as Record const runtimeActive = !!(task.agentStatus && task.agentStatus !== 'idle') const deliverables = stringList(task.deliverables) const acceptanceCriteria = stringList(task.acceptanceCriteria) const dependencyIds = stringList(task.dependencies) const assignmentSummary = infoPairsFromRecord({ role_id: residentAssignment.role_id, employee_id: residentAssignment.employee_id, manager_role_id: residentAssignment.manager_role_id, team_id: residentAssignment.team_id, seat_id: residentAssignment.seat_id, work_item_turn_type: residentAssignment.work_item_turn_type, resident_status: residentAssignment.resident_status, }) const memberStateSummary = infoPairsFromRecord({ status: memberSessionState.status, current_turn_mode: memberSessionState.current_turn_mode, manager_role_id: memberSessionState.manager_role_id, actionable_inbox_count: memberSessionState.actionable_inbox_count, protocol_backlog_count: memberSessionState.protocol_backlog_count, notification_backlog_count: memberSessionState.notification_backlog_count, }) const promptContext = String(task.employeeAssignment?.promptContext ?? '').trim() const deltaContext = String(task.employeeAssignment?.deltaContext ?? '').trim() const linkedTaskId = getLinkedRuntimeTaskId(task) const isWorkItem = !!(task.workItemId || task.workItemProjectionId || linkedTaskId) return (
{linkedTaskId && (onOpenLinkedSession || onOpenExecutionPanel) && ( )}
{(task.workItemRoleName ?? task.title).charAt(0).toUpperCase()}
{task.title} {[task.workItemRoleName, task.phase].filter(Boolean).join(' · ')}
{runtimeActive && (
{task.agentStatus === 'tool_active' && task.currentTool ? task.currentTool : AGENT_STATUS_LABEL[task.agentStatus!] ?? task.agentStatus} {liveAssignees.length > 0 && ( {liveAssignees.map(agent => agent.name).join(', ')} )}
)}

{isWorkItem ? 'Work Item' : 'Task'}

{task.displayId} {priorityMeta && {priorityMeta.label}} {task.workItemRoleName && {task.workItemRoleName}} {task.managerRoleId && Manager: {task.managerRoleId}} {task.scopeKey && {task.scopeKey}}
{task.description && (
)}
{task.originalMessage && (

Session Goal

{task.originalMessage}
)} {task.planningContext && (

Planning Context

{task.planningContext}
)} {deliverables.length > 0 && (

Deliverables

    {deliverables.map(item => (
  • {item}
  • ))}
)} {acceptanceCriteria.length > 0 && (

Acceptance Criteria

    {acceptanceCriteria.map(item => (
  • {item}
  • ))}
)} {(task.delegationRationale || task.nonOverlapGuard || task.coordinationNotes) && (

Delegation Notes

{task.delegationRationale &&
{task.delegationRationale}
} {task.nonOverlapGuard &&
{task.nonOverlapGuard}
} {task.coordinationNotes &&
{task.coordinationNotes}
}
)} {dependencyIds.length > 0 && (

Dependencies

    {dependencyIds.map(depId => (
  • {depId}
  • ))}
)} {(assignmentSummary.length > 0 || memberStateSummary.length > 0 || linkedSession) && (

Role Runtime Context

{linkedSession && (
Runtime Session: {linkedSession.title} Status: {linkedSession.status}
)} {assignmentSummary.length > 0 && (
{assignmentSummary.map(([label, value]) => (
{label} {value}
))}
)} {memberStateSummary.length > 0 && (
{memberStateSummary.map(([label, value]) => (
{label} {value}
))}
)}
)} {task.handoffContext && (

Handoff Context

{task.handoffContext}
)} {promptContext && (

Role Prompt Context

)} {deltaContext && (

Role Delta Context

{deltaContext}
)} {Object.keys(ownershipContract).length > 0 && (

Ownership Contract

{prettyJson(ownershipContract)}
)} {task.progressLog && task.progressLog.length > 0 && (

Activity

    {task.progressLog.map((entry, index) => (
  • {new Date(entry.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit', })} {entry.summary}
  • ))}
)} {/* Runtime session transcript — live chat of the agent processing this work item */}

Runtime Session Activity

{linkedSession && linkedSessionMessages && linkedSessionMessages.length > 0 ? (
) : linkedSession ? (

Runtime session has no visible messages yet.

) : (

No Runtime Session linked yet.

)}
) }