feat(FN-1748): merge fusion/fn-1748

This commit is contained in:
gsxdsm
2026-04-14 22:45:59 -07:00
parent 6e2236c0ae
commit 42da5e80cb
34 changed files with 133 additions and 151 deletions

View File

@@ -1,8 +1,8 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import { Pencil, Bot, X, ChevronDown } from "lucide-react";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import type { Task, TaskDetail, TaskAttachment, Column, MergeResult, PrInfo, Settings, AgentLogEntry, Agent } from "@fusion/core";
import type { Task, TaskDetail, TaskAttachment, Column, MergeResult, Settings, AgentLogEntry, Agent } from "@fusion/core";
import { COLUMN_LABELS, VALID_TRANSITIONS } from "@fusion/core";
import { uploadAttachment, deleteAttachment, updateTask, pauseTask, unpauseTask, fetchTaskDetail, fetchSettings, requestSpecRevision, rebuildTaskSpec, approvePlan, rejectPlan, refineTask, fetchWorkflowResults, assignTask, fetchAgents, fetchAgent } from "../api";
import type { WorkflowStepResult } from "@fusion/core";
@@ -23,24 +23,10 @@ interface ModelSelection {
modelId?: string;
}
function normalizeModelField(value: string | null | undefined): string | undefined {
function _normalizeModelField(value: string | null | undefined): string | undefined {
return value ?? undefined;
}
function getExecutorSelection(task: Task | TaskDetail): ModelSelection {
return {
provider: normalizeModelField(task.modelProvider),
modelId: normalizeModelField(task.modelId),
};
}
function getValidatorSelection(task: Task | TaskDetail): ModelSelection {
return {
provider: normalizeModelField(task.validatorModelProvider),
modelId: normalizeModelField(task.validatorModelId),
};
}
/**
* Resolve the effective executor model following the engine's resolution order:
* 1. Per-task modelProvider/modelId (both must be set)
@@ -359,7 +345,7 @@ export function TaskDetailModal({
})
.catch((err: any) => {
if (!cancelled) {
addToast(`Failed to load workflow results: ${err.message}`, "error");
addToast(`Failed to load workflow results: ${err instanceof Error ? err.message : String(err)}`, "error");
}
})
.finally(() => {
@@ -557,8 +543,8 @@ export function TaskDetailModal({
setEditPendingImages([]);
addToast(`Updated ${task.id}`, "success");
setIsEditing(false);
} catch (err: any) {
addToast(`Failed to update ${task.id}: ${err.message}`, "error");
} catch (err: unknown) {
addToast(`Failed to update ${task.id}: ${err instanceof Error ? err.message : String(err)}`, "error");
} finally {
if (mountedRef.current) {
setIsSaving(false);
@@ -570,8 +556,8 @@ export function TaskDetailModal({
try {
await updateTask(task.id, { description }, projectId);
addToast("Description saved", "success");
} catch (err: any) {
addToast(`Failed to save: ${err.message}`, "error");
} catch (err: unknown) {
addToast(`Failed to save: ${err instanceof Error ? err.message : String(err)}`, "error");
}
}, [task.id, addToast, projectId]);
@@ -620,8 +606,8 @@ export function TaskDetailModal({
await onMoveTask(task.id, column);
onClose();
addToast(`Moved to ${COLUMN_LABELS[column]}`, "success");
} catch (err: any) {
addToast(err.message, "error");
} catch (err: unknown) {
addToast(err instanceof Error ? err.message : String(err), "error");
}
},
[task.id, onMoveTask, onClose, addToast],
@@ -633,8 +619,8 @@ export function TaskDetailModal({
await onDeleteTask(task.id);
onClose();
addToast(`Deleted ${task.id}`, "info");
} catch (err: any) {
addToast(err.message, "error");
} catch (err: unknown) {
addToast(err instanceof Error ? err.message : String(err), "error");
}
}, [task.id, onDeleteTask, onClose, addToast]);
@@ -650,7 +636,7 @@ export function TaskDetailModal({
addToast(msg, "success");
})
.catch((err: any) => {
addToast(err.message, "error");
addToast(err instanceof Error ? err.message : String(err), "error");
});
}, [task.id, onMergeTask, onClose, addToast]);
@@ -660,8 +646,8 @@ export function TaskDetailModal({
await onRetryTask(task.id);
onClose();
addToast(`Retrying ${task.id}...`, "info");
} catch (err: any) {
addToast(err.message, "error");
} catch (err: unknown) {
addToast(err instanceof Error ? err.message : String(err), "error");
}
}, [task.id, onRetryTask, onClose, addToast]);
@@ -672,8 +658,8 @@ export function TaskDetailModal({
const newTask = await onDuplicateTask(task.id);
onClose();
addToast(`Duplicated ${task.id}${newTask.id}`, "success");
} catch (err: any) {
addToast(err.message, "error");
} catch (err: unknown) {
addToast(err instanceof Error ? err.message : String(err), "error");
}
}, [task.id, onDuplicateTask, onClose, addToast]);
@@ -687,8 +673,8 @@ export function TaskDetailModal({
addToast(`Paused ${task.id}`, "success");
}
onClose();
} catch (err: any) {
addToast(err.message, "error");
} catch (err: unknown) {
addToast(err instanceof Error ? err.message : String(err), "error");
}
}, [task.id, task.paused, onClose, addToast]);
@@ -697,8 +683,8 @@ export function TaskDetailModal({
await approvePlan(task.id, projectId);
addToast(`Plan approved — ${task.id} moved to Todo`, "success");
onClose();
} catch (err: any) {
addToast(err.message, "error");
} catch (err: unknown) {
addToast(err instanceof Error ? err.message : String(err), "error");
}
}, [task.id, onClose, addToast]);
@@ -708,8 +694,8 @@ export function TaskDetailModal({
await rejectPlan(task.id, projectId);
addToast(`Plan rejected — ${task.id} returned to Triage for re-specification`, "info");
onClose();
} catch (err: any) {
addToast(err.message, "error");
} catch (err: unknown) {
addToast(err instanceof Error ? err.message : String(err), "error");
}
}, [task.id, onClose, addToast]);
@@ -719,8 +705,8 @@ export function TaskDetailModal({
await rebuildTaskSpec(task.id, projectId);
onClose();
addToast(`Respecifying ${task.id}...`, "info");
} catch (err: any) {
addToast(err.message, "error");
} catch (err: unknown) {
addToast(err instanceof Error ? err.message : String(err), "error");
}
}, [task.id, projectId, onClose, addToast]);
@@ -771,8 +757,8 @@ export function TaskDetailModal({
const newTask = await refineTask(task.id, refineFeedback.trim(), projectId);
addToast(`Refinement task created: ${newTask.id}`, "success");
onClose();
} catch (err: any) {
addToast(err.message, "error");
} catch (err: unknown) {
addToast(err instanceof Error ? err.message : String(err), "error");
} finally {
setIsRefining(false);
}
@@ -784,8 +770,8 @@ export function TaskDetailModal({
const attachment = await uploadAttachment(task.id, file, projectId);
setAttachments((prev) => [...prev, attachment]);
addToast("Screenshot attached", "success");
} catch (err: any) {
addToast(err.message, "error");
} catch (err: unknown) {
addToast(err instanceof Error ? err.message : String(err), "error");
} finally {
setUploading(false);
}
@@ -839,8 +825,8 @@ export function TaskDetailModal({
await deleteAttachment(task.id, filename, projectId);
setAttachments((prev) => prev.filter((a) => a.filename !== filename));
addToast("Attachment deleted", "info");
} catch (err: any) {
addToast(err.message, "error");
} catch (err: unknown) {
addToast(err instanceof Error ? err.message : String(err), "error");
}
}, [task.id, addToast]);
@@ -852,9 +838,9 @@ export function TaskDetailModal({
const updatedTask = await updateTask(task.id, { enabledWorkflowSteps }, projectId);
addToast("Workflow steps updated", "success");
onTaskUpdated?.(updatedTask);
} catch (err: any) {
} catch (err: unknown) {
setWorkflowEnabledSteps(previousSteps);
addToast(`Failed to update workflow steps: ${err.message}`, "error");
addToast(`Failed to update workflow steps: ${err instanceof Error ? err.message : String(err)}`, "error");
}
}, [task.id, projectId, workflowEnabledSteps, onTaskUpdated, addToast]);
@@ -864,8 +850,8 @@ export function TaskDetailModal({
const loadedAgents = await fetchAgents(undefined, projectId);
setAgents(loadedAgents);
setShowAgentPicker(true);
} catch (err: any) {
addToast(`Failed to load agents: ${err.message}`, "error");
} catch (err: unknown) {
addToast(`Failed to load agents: ${err instanceof Error ? err.message : String(err)}`, "error");
setShowAgentPicker(false);
} finally {
setAgentsLoading(false);
@@ -884,8 +870,8 @@ export function TaskDetailModal({
setShowAgentPicker(false);
onTaskUpdated?.(updatedTask);
addToast("Assigned agent updated", "success");
} catch (err: any) {
addToast(`Failed to assign agent: ${err.message}`, "error");
} catch (err: unknown) {
addToast(`Failed to assign agent: ${err instanceof Error ? err.message : String(err)}`, "error");
}
}, [task.id, projectId, agents, onTaskUpdated, addToast]);
@@ -896,8 +882,8 @@ export function TaskDetailModal({
setShowAgentPicker(false);
onTaskUpdated?.(updatedTask);
addToast("Agent unassigned", "success");
} catch (err: any) {
addToast(`Failed to unassign agent: ${err.message}`, "error");
} catch (err: unknown) {
addToast(`Failed to unassign agent: ${err instanceof Error ? err.message : String(err)}`, "error");
}
}, [task.id, projectId, onTaskUpdated, addToast]);
@@ -906,9 +892,10 @@ export function TaskDetailModal({
setDependencies(newDeps);
try {
await updateTask(task.id, { dependencies: newDeps }, projectId);
} catch (err: any) {
} catch (err: unknown) {
setDependencies(dependencies);
addToast(err.message, "error");
const message = err instanceof Error ? err instanceof Error ? err.message : String(err) : String(err);
addToast(message, "error");
}
}, [task.id, dependencies, addToast]);
@@ -918,9 +905,10 @@ export function TaskDetailModal({
setDependencies(newDeps);
try {
await updateTask(task.id, { dependencies: newDeps }, projectId);
} catch (err: any) {
} catch (err: unknown) {
setDependencies(dependencies);
addToast(err.message, "error");
const message = err instanceof Error ? err instanceof Error ? err.message : String(err) : String(err);
addToast(message, "error");
}
}, [task.id, dependencies, addToast]);
@@ -928,7 +916,7 @@ export function TaskDetailModal({
try {
const detail = await fetchTaskDetail(depId, projectId);
onOpenDetail(detail);
} catch (err: any) {
} catch (err: unknown) {
addToast(`Failed to load dependency ${depId}`, "error");
}
}, [onOpenDetail, addToast]);
@@ -943,8 +931,8 @@ export function TaskDetailModal({
if (fullDetail) {
fullDetail.prompt = newContent;
}
} catch (err: any) {
addToast(err.message, "error");
} catch (err: unknown) {
addToast(err instanceof Error ? err.message : String(err), "error");
throw err;
} finally {
setIsSavingSpec(false);
@@ -958,11 +946,12 @@ export function TaskDetailModal({
addToast("AI revision requested. Task moved to triage.", "success");
// Task has been moved to triage, close modal
onClose();
} catch (err: any) {
if (err.message?.includes("in-review") || err.message?.includes("done")) {
} catch (err: unknown) {
const message = err instanceof Error ? err.message : String(err);
if (message?.includes("in-review") || message?.includes("done")) {
addToast("Cannot request revision: Task must be in 'todo' or 'in-progress' column.", "error");
} else {
addToast(err.message, "error");
addToast(message, "error");
}
} finally {
setIsRequestingRevision(false);