feat(FN-1748): merge fusion/fn-1748
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user