feat(FN-1748): merge fusion/fn-1748
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import { memo, useCallback, useMemo } from "react";
|
||||
import {
|
||||
GitPullRequest,
|
||||
GitMerge,
|
||||
CheckCircle,
|
||||
XCircle,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { X, History, Trash2, Filter, RefreshCw, CheckCircle, XCircle, ArrowRight, Plus, Settings, AlertCircle, Loader2, Folder } from "lucide-react";
|
||||
import { clearActivityLog, type ActivityLogEntry, type ActivityEventType, type ActivityFeedEntry } from "../api";
|
||||
import { useActivityLog } from "../hooks/useActivityLog";
|
||||
@@ -81,6 +81,7 @@ export function ActivityLogModal({
|
||||
onProjectFilterChange,
|
||||
currentProject,
|
||||
}: ActivityLogModalProps) {
|
||||
// tasks parameter reserved for future use when filtering activity by task
|
||||
const [filteredType, setFilteredType] = useState<ActivityEventType | "all">("all");
|
||||
const [filteredProjectId, setFilteredProjectId] = useState<string | "all">(projectId || "all");
|
||||
const [showConfirmClear, setShowConfirmClear] = useState(false);
|
||||
@@ -136,7 +137,7 @@ export function ActivityLogModal({
|
||||
await clearActivityLog();
|
||||
refresh();
|
||||
setShowConfirmClear(false);
|
||||
} catch (err) {
|
||||
} catch {
|
||||
// Error handled by hook
|
||||
setShowConfirmClear(false);
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ export function AddNodeModal({ isOpen, onClose, onSubmit, addToast }: AddNodeMod
|
||||
|
||||
try {
|
||||
await onSubmit(input);
|
||||
addToast(`Node \"${input.name}\" registered`, "success");
|
||||
addToast(`Node "${input.name}" registered`, "success");
|
||||
closeModal();
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Failed to register node";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect, useCallback, useRef, useMemo } from "react";
|
||||
import type { JSX } from "react";
|
||||
import { X, Plus, Play, Pause, Square, Activity, Heart, Trash2, RefreshCw, Bot, LayoutGrid, List, Filter } from "lucide-react";
|
||||
import { X, Plus, Play, Pause, Square, Trash2, RefreshCw, Bot, LayoutGrid, List, Filter } from "lucide-react";
|
||||
import type { Agent, AgentCapability, AgentState } from "../api";
|
||||
import { fetchAgents, createAgent, updateAgent, updateAgentState, deleteAgent } from "../api";
|
||||
import { getScopedItem, setScopedItem } from "../utils/projectStorage";
|
||||
@@ -158,7 +158,7 @@ export function AgentListModal({ isOpen, onClose, addToast, projectId }: AgentLi
|
||||
}
|
||||
};
|
||||
|
||||
const handleRoleKeyDown = (e: React.KeyboardEvent, agentId: string) => {
|
||||
const handleRoleKeyDown = (e: React.KeyboardEvent, _agentId: string) => {
|
||||
if (e.key === "Escape") {
|
||||
setEditingRoleForAgent(null);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect, useCallback, useRef, useMemo } from "react";
|
||||
import type { JSX } from "react";
|
||||
import { Plus, Play, Pause, Square, Activity, Heart, Trash2, RefreshCw, Bot, LayoutGrid, List, ChevronRight, ChevronDown, GitBranch, Filter, Upload, Network } from "lucide-react";
|
||||
import { Plus, Play, Pause, Square, Activity, Trash2, RefreshCw, Bot, LayoutGrid, List, ChevronRight, ChevronDown, GitBranch, Filter, Upload, Network } from "lucide-react";
|
||||
import type { Agent, AgentCapability, AgentState, OrgTreeNode } from "../api";
|
||||
import { fetchAgents, updateAgent, updateAgentState, deleteAgent, startAgentRun, fetchOrgTree } from "../api";
|
||||
import { AgentDetailView } from "./AgentDetailView";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ProjectInfo } from "../api";
|
||||
import type { ColorTheme, Column, MergeResult, Task, TaskCreateInput, ThemeMode } from "@fusion/core";
|
||||
import type { ColorTheme, Column, MergeResult, Task, ThemeMode } from "@fusion/core";
|
||||
import type { UseProjectActionsResult } from "../hooks/useProjectActions";
|
||||
import type { ModalManager } from "../hooks/useModalManager";
|
||||
import type { UseTaskHandlersResult } from "../hooks/useTaskHandlers";
|
||||
|
||||
@@ -95,11 +95,6 @@ export function CustomModelDropdown({
|
||||
}, [favoriteModels, filteredModels]);
|
||||
|
||||
// Sort providers: favorites first (in order), then alphabetically
|
||||
// Exclude providers that are already favorited as models (they appear at top as pinned rows)
|
||||
const favoritedProviderSet = new Set(favoriteModels.map((fullId) => {
|
||||
const idx = fullId.indexOf("/");
|
||||
return idx !== -1 ? fullId.slice(0, idx) : fullId;
|
||||
}));
|
||||
const sortedProviderEntries = useMemo(() => {
|
||||
const entries = Object.entries(modelsByProvider);
|
||||
const favoritesSet = new Set(favoriteProviders);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMemo } from "react";
|
||||
import type { Task } from "@fusion/core";
|
||||
import { Activity, AlertTriangle, Clock, Pause, Play, Zap } from "lucide-react";
|
||||
import { AlertTriangle, Clock, Pause, Play, Zap } from "lucide-react";
|
||||
import { useExecutorStats } from "../hooks/useExecutorStats";
|
||||
import type { ExecutorState, AiSessionSummary } from "../api";
|
||||
import { BackgroundTasksIndicator } from "./BackgroundTasksIndicator";
|
||||
|
||||
@@ -319,7 +319,6 @@ export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId
|
||||
// Determine state flags
|
||||
const hasRemotes = remotes.length > 0;
|
||||
const singleRemote = remotes.length === 1;
|
||||
const multipleRemotes = remotes.length > 1;
|
||||
|
||||
// Tab-specific counts
|
||||
const importedIssueCount = issues.filter((issue) => importedUrls.has(issue.html_url)).length;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useCallback, useMemo, Fragment, useEffect, useRef } from "react";
|
||||
import { LayoutGrid, List as ListIcon, ArrowUpDown, ArrowUp, ArrowDown, Link, Columns3, EyeOff, Eye, ChevronRight } from "lucide-react";
|
||||
import { ArrowUpDown, ArrowUp, ArrowDown, Link, Columns3, EyeOff, Eye, ChevronRight } from "lucide-react";
|
||||
import type { Task, TaskDetail, Column, TaskStep, TaskCreateInput } from "@fusion/core";
|
||||
import { COLUMN_LABELS, COLUMNS } from "@fusion/core";
|
||||
import { batchUpdateTaskModels } from "../api";
|
||||
@@ -122,8 +122,6 @@ interface ListViewProps {
|
||||
onTasksUpdated?: (updatedTasks: Task[]) => void;
|
||||
/** Project ID for multi-project context (optional) */
|
||||
projectId?: string;
|
||||
/** Project name for display (optional) */
|
||||
projectName?: string;
|
||||
/** Project-level stuck task timeout in milliseconds (undefined = disabled) */
|
||||
taskStuckTimeoutMs?: number;
|
||||
/** External search query from header search (defaults to "") */
|
||||
@@ -161,7 +159,6 @@ export function ListView({
|
||||
onSubtaskBreakdown,
|
||||
onTasksUpdated,
|
||||
projectId,
|
||||
projectName,
|
||||
taskStuckTimeoutMs,
|
||||
searchQuery = "",
|
||||
lastFetchTimeMs,
|
||||
|
||||
@@ -117,8 +117,6 @@ export function PluginManager({ addToast, projectId }: PluginManagerProps) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentPlugins = pluginsRef.current;
|
||||
|
||||
switch (payload.transition) {
|
||||
case "installing":
|
||||
case "enabled":
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { memo, useCallback, useState } from "react";
|
||||
import { Play, Pause, AlertCircle, Loader2, MoreHorizontal, Trash2, Folder, ArrowRight } from "lucide-react";
|
||||
import { Play, Pause, AlertCircle, Loader2, Trash2, Folder, ArrowRight } from "lucide-react";
|
||||
import type { RegisteredProject, ProjectHealth, ProjectStatus } from "@fusion/core";
|
||||
import type { NodeInfo } from "../api";
|
||||
|
||||
|
||||
@@ -102,12 +102,13 @@ export function ProjectOverview({
|
||||
case "name":
|
||||
comparison = a.project.name.localeCompare(b.project.name);
|
||||
break;
|
||||
case "activity":
|
||||
case "activity": {
|
||||
const aTime = a.project.lastActivityAt || a.health?.lastActivityAt || a.project.updatedAt;
|
||||
const bTime = b.project.lastActivityAt || b.health?.lastActivityAt || b.project.updatedAt;
|
||||
comparison = new Date(bTime).getTime() - new Date(aTime).getTime();
|
||||
break;
|
||||
case "status":
|
||||
}
|
||||
case "status": {
|
||||
const statusOrder: Record<ProjectStatus, number> = {
|
||||
errored: 0,
|
||||
initializing: 1,
|
||||
@@ -116,6 +117,7 @@ export function ProjectOverview({
|
||||
};
|
||||
comparison = statusOrder[a.project.status] - statusOrder[b.project.status];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return sortDirection === "asc" ? comparison : -comparison;
|
||||
|
||||
@@ -3,7 +3,6 @@ import { Calendar, Webhook, Code, Zap } from "lucide-react";
|
||||
import type {
|
||||
Routine,
|
||||
RoutineCreateInput,
|
||||
RoutineUpdateInput,
|
||||
RoutineTrigger,
|
||||
RoutineTriggerType,
|
||||
RoutineCronTrigger,
|
||||
@@ -22,7 +21,7 @@ function isLikelyCron(expr: string): boolean {
|
||||
const parts = expr.trim().split(/\s+/);
|
||||
if (parts.length !== 5) return false;
|
||||
// Each field should contain digits, *, /, -, or ,
|
||||
return parts.every((p) => /^[\d*,/\-]+$/.test(p));
|
||||
return parts.every((p) => /^[\d*,/-]+$/.test(p));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -38,7 +38,7 @@ function isLikelyCron(expr: string): boolean {
|
||||
const parts = expr.trim().split(/\s+/);
|
||||
if (parts.length !== 5) return false;
|
||||
// Each field should contain digits, *, /, -, or ,
|
||||
return parts.every((p) => /^[\d*,/\-]+$/.test(p));
|
||||
return parts.every((p) => /^[\d*,/-]+$/.test(p));
|
||||
}
|
||||
|
||||
type ScheduleMode = "simple" | "advanced";
|
||||
|
||||
@@ -431,8 +431,6 @@ export function ScheduledTasksModal({ onClose, addToast }: ScheduledTasksModalPr
|
||||
// Determine if we're in "list" view for showing the "New" button
|
||||
const isShowingList =
|
||||
activeTab === "schedules" ? view === "list" && schedules.length > 0 : routineView === "list" && routines.length > 0;
|
||||
const isShowingEmptyState =
|
||||
activeTab === "schedules" ? view === "list" && schedules.length === 0 && !loading : routineView === "list" && routines.length === 0;
|
||||
|
||||
return (
|
||||
<div className="modal-overlay open" onClick={handleOverlayClick}>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useState, useEffect, useCallback, useRef } from "react";
|
||||
import { Globe, Folder } from "lucide-react";
|
||||
import { THINKING_LEVELS, PROMPT_KEY_CATALOG, isGlobalSettingsKey, isProjectSettingsKey } from "@fusion/core";
|
||||
import type { Settings, GlobalSettings, ThemeMode, ColorTheme, ModelPreset, NtfyNotificationEvent, PromptKey, AgentPromptsConfig } from "@fusion/core";
|
||||
import { THINKING_LEVELS, isGlobalSettingsKey, isProjectSettingsKey } from "@fusion/core";
|
||||
import type { Settings, GlobalSettings, ThemeMode, ColorTheme, ModelPreset, NtfyNotificationEvent, AgentPromptsConfig } from "@fusion/core";
|
||||
import { fetchSettings, updateSettings, updateGlobalSettings, fetchAuthStatus, loginProvider, logoutProvider, saveApiKey, clearApiKey, fetchModels, testNtfyNotification, fetchBackups, createBackup, exportSettings, importSettings, fetchMemory, saveMemory, fetchGlobalConcurrency, updateGlobalConcurrency } from "../api";
|
||||
import type { AuthProvider, ModelInfo, BackupListResponse, SettingsExportData, MemoryBackendCapabilities } from "../api";
|
||||
import type { AuthProvider, ModelInfo, BackupListResponse, SettingsExportData } from "../api";
|
||||
import { useMemoryBackendStatus } from "../hooks/useMemoryBackendStatus";
|
||||
import type { ToastType } from "../hooks/useToast";
|
||||
import { ThemeSelector } from "./ThemeSelector";
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { FileText, ChevronDown, ChevronUp, Plus, Trash2, History, X } from "lucide-react";
|
||||
import { FileText, ChevronDown, ChevronUp, Plus, Trash2, History } from "lucide-react";
|
||||
import type { Task, TaskDocument, TaskDocumentRevision } from "@fusion/core";
|
||||
import type { ToastType } from "../hooks/useToast";
|
||||
import {
|
||||
fetchTaskDocuments,
|
||||
fetchTaskDocument,
|
||||
fetchTaskDocumentRevisions,
|
||||
putTaskDocument,
|
||||
deleteTaskDocument,
|
||||
|
||||
@@ -69,8 +69,9 @@ export async function retryDynamicImport<T>(
|
||||
/** Whether the current device is likely mobile (touch-primary, small viewport). */
|
||||
function isMobileDevice(): boolean {
|
||||
if (typeof window === "undefined") return false;
|
||||
const nav = navigator as Navigator & { maxTouchPoints?: number };
|
||||
const hasTouchScreen =
|
||||
"ontouchstart" in window || (navigator as any).maxTouchPoints > 0;
|
||||
"ontouchstart" in window || (nav.maxTouchPoints ?? 0) > 0;
|
||||
const isNarrow = window.innerWidth <= 768;
|
||||
return hasTouchScreen && isNarrow;
|
||||
}
|
||||
@@ -601,7 +602,9 @@ export function TerminalModal({ isOpen, onClose, initialCommand, projectId }: Te
|
||||
if (xtermRef.current) {
|
||||
const currentSize = xtermRef.current.options.fontSize || 14;
|
||||
xtermRef.current.options.fontSize = Math.min(currentSize + 1, 32);
|
||||
fitAddonRef.current && (fitAddonRef.current as InstanceType<typeof FitAddon>).fit();
|
||||
if (fitAddonRef.current) {
|
||||
(fitAddonRef.current as InstanceType<typeof FitAddon>).fit();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -612,7 +615,9 @@ export function TerminalModal({ isOpen, onClose, initialCommand, projectId }: Te
|
||||
if (xtermRef.current) {
|
||||
const currentSize = xtermRef.current.options.fontSize || 14;
|
||||
xtermRef.current.options.fontSize = Math.max(currentSize - 1, 8);
|
||||
fitAddonRef.current && (fitAddonRef.current as InstanceType<typeof FitAddon>).fit();
|
||||
if (fitAddonRef.current) {
|
||||
(fitAddonRef.current as InstanceType<typeof FitAddon>).fit();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -622,7 +627,9 @@ export function TerminalModal({ isOpen, onClose, initialCommand, projectId }: Te
|
||||
e.preventDefault();
|
||||
if (xtermRef.current) {
|
||||
xtermRef.current.options.fontSize = 14;
|
||||
fitAddonRef.current && (fitAddonRef.current as InstanceType<typeof FitAddon>).fit();
|
||||
if (fitAddonRef.current) {
|
||||
(fitAddonRef.current as InstanceType<typeof FitAddon>).fit();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -726,8 +733,9 @@ export function TerminalModal({ isOpen, onClose, initialCommand, projectId }: Te
|
||||
// Restart the active tab's session
|
||||
try {
|
||||
await restartActiveTab();
|
||||
} catch (err: any) {
|
||||
setError(err.message || "Failed to restart terminal session");
|
||||
} catch (err: unknown) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
setError(message || "Failed to restart terminal session");
|
||||
}
|
||||
}, [restartActiveTab]);
|
||||
|
||||
|
||||
@@ -209,8 +209,8 @@ export interface MissionWithHierarchy extends Mission {
|
||||
export type MissionEventType = CoreMissionEventType;
|
||||
|
||||
/** Mission lifecycle event persisted in the mission event log. */
|
||||
export interface MissionEvent extends CoreMissionEvent {}
|
||||
export type MissionEvent = CoreMissionEvent;
|
||||
|
||||
/** Computed mission health snapshot returned by observability APIs. */
|
||||
export interface MissionHealth extends CoreMissionHealth {}
|
||||
export type MissionHealth = CoreMissionHealth;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user