diff --git a/.changeset/task-chat-effective-model-icons.md b/.changeset/task-chat-effective-model-icons.md new file mode 100644 index 0000000000..5db4a547a1 --- /dev/null +++ b/.changeset/task-chat-effective-model-icons.md @@ -0,0 +1,5 @@ +--- +"@runfusion/fusion": patch +--- + +Show provider icons in task detail chat for default-backed executor, reviewer, planner, and merger models. diff --git a/packages/dashboard/app/components/TaskChatTab.tsx b/packages/dashboard/app/components/TaskChatTab.tsx index 31acdf6cb7..faf79c5a54 100644 --- a/packages/dashboard/app/components/TaskChatTab.tsx +++ b/packages/dashboard/app/components/TaskChatTab.tsx @@ -25,6 +25,7 @@ interface TaskChatTabProps { onTaskUpdated?: (task: Task) => void; expanded?: boolean; onToggleExpanded?: () => void; + effectiveModels?: Partial>; } type AgentLogRole = AgentRole | undefined; @@ -122,12 +123,28 @@ function getRuntimeModelForRole(entries: readonly AgentLogEntry[], role: AgentLo return null; } -function getModelForRole(task: Task | TaskDetail, role: AgentLogRole, entries: readonly AgentLogEntry[]): TaskChatModelInfo | null { +function getEffectiveModelForRole( + effectiveModels: TaskChatTabProps["effectiveModels"] | undefined, + role: AgentLogRole, +): TaskChatModelInfo | null { + if (!role) return null; + return effectiveModels?.[role] ?? null; +} + +function getModelForRole( + task: Task | TaskDetail, + role: AgentLogRole, + entries: readonly AgentLogEntry[], + effectiveModels?: TaskChatTabProps["effectiveModels"], +): TaskChatModelInfo | null { /* FNXC:TaskDetailChat 2026-06-23-21:18: Task-detail chat agent headers should identify the AI provider actually backing each role, not a generic/role avatar. Prefer explicit task model overrides because they are stable before logs stream, then fall back to the runtime "using model" log marker emitted by active planner/executor/reviewer sessions. Merger output uses the validator/reviewer lane provider because merge-fix/review flows share that model family in the UI. + + FNXC:TaskDetailChat 2026-06-23-00:54: + Default executor models such as OpenAI Codex GPT-5.5 can resolve through settings rather than task overrides or log markers. Task chat receives the same effective model resolution used by the task-detail model header so role icons match Chat and Agent Log instead of falling back to CPU for default-backed agents. */ - return getExplicitModelForRole(task, role) ?? getRuntimeModelForRole(entries, role); + return getExplicitModelForRole(task, role) ?? getRuntimeModelForRole(entries, role) ?? getEffectiveModelForRole(effectiveModels, role); } function TaskChatAgentIcon({ label, modelInfo, role }: { label: string; modelInfo: TaskChatModelInfo | null; role: AgentLogRole }) { @@ -532,7 +549,7 @@ function TaskChatUserMessage({ message }: { message: UserChatMessage }) { ); } -export function TaskChatTab({ task, projectId, active, addToast, sessionLive, onTaskUpdated, expanded = false, onToggleExpanded }: TaskChatTabProps) { +export function TaskChatTab({ task, projectId, active, addToast, sessionLive, onTaskUpdated, expanded = false, onToggleExpanded, effectiveModels }: TaskChatTabProps) { const { t } = useTranslation("app"); const { entries, loading, loadMore, hasMore, loadingMore } = useAgentLogs(task.id, active, projectId); const [draft, setDraft] = useState(""); @@ -868,7 +885,7 @@ export function TaskChatTab({ task, projectId, active, addToast, sessionLive, on const segments = segmentGroupEntries(item.entries); const latestEntryTimestamp = item.entries[item.entries.length - 1]?.timestamp ?? ""; const relativeTime = formatRelativeTimeAgo(latestEntryTimestamp); - const modelInfo = getModelForRole(task, item.role, item.entries); + const modelInfo = getModelForRole(task, item.role, item.entries, effectiveModels); return (
diff --git a/packages/dashboard/app/components/TaskDetailModal.tsx b/packages/dashboard/app/components/TaskDetailModal.tsx index 94729e1bc4..2aafea8b95 100644 --- a/packages/dashboard/app/components/TaskDetailModal.tsx +++ b/packages/dashboard/app/components/TaskDetailModal.tsx @@ -263,6 +263,11 @@ function resolveEffectivePlanning( return resolveTaskPlanningModel(task, settings); } +function toTaskChatModelInfo(model: ModelSelection): { provider: string; modelId?: string } | null { + if (!model.provider) return null; + return model.modelId ? { provider: model.provider, modelId: model.modelId } : { provider: model.provider }; +} + function getStepStatusColor(status: string): string { switch (status) { case "done": @@ -3304,6 +3309,12 @@ export function TaskDetailContent({ onTaskUpdated={handleChatTaskUpdated} expanded={chatExpanded} onToggleExpanded={() => setChatExpanded((value) => !value)} + effectiveModels={{ + triage: toTaskChatModelInfo(resolveEffectivePlanning(workingTask, agentLogEntries, settings)), + executor: toTaskChatModelInfo(resolveEffectiveExecutor(workingTask, agentLogEntries, assignedAgent, settings)), + reviewer: toTaskChatModelInfo(resolveEffectiveValidator(workingTask, agentLogEntries, assignedAgent, settings)), + merger: toTaskChatModelInfo(resolveEffectiveValidator(workingTask, agentLogEntries, assignedAgent, settings)), + }} /> ) : activeTab === "logs" ? ( diff --git a/packages/dashboard/app/components/__tests__/TaskChatTab.test.tsx b/packages/dashboard/app/components/__tests__/TaskChatTab.test.tsx index f4fa1b6665..9d54fe43f4 100644 --- a/packages/dashboard/app/components/__tests__/TaskChatTab.test.tsx +++ b/packages/dashboard/app/components/__tests__/TaskChatTab.test.tsx @@ -468,6 +468,24 @@ describe("TaskChatTab", () => { expect(document.querySelector(".task-chat-provider-icon [data-provider='anthropic']")).toBeTruthy(); }); + it("renders provider icons for task chat roles from effective default models", () => { + mockLogs([ + makeEntry({ agent: "executor", text: "executor output without model marker" }), + ]); + + render( + , + ); + + expect(document.querySelector(".task-chat-provider-icon [data-provider='openai-codex']")).toBeTruthy(); + expect(screen.queryByLabelText("Executor: model provider unknown")).not.toBeInTheDocument(); + }); + it("groups consecutive entries by agent role", () => { mockLogs([ makeEntry({ agent: "executor", text: "first" }),