refactor: eliminate ~400 no-explicit-any warnings across the workspace

Parallel subagent pass: four typescript-pro agents on non-overlapping scopes.

Patterns applied:
- catch (err: any) { ... err.message ... } → catch (err) { ... getErrorMessage(err) ... }
  using the new @fusion/core helper. Bare catch {} where the error was unused.
- SQLite row types: defined typed XxxRow interfaces per table and cast
  .all()/.get() results via `as unknown as XxxRow[]` (the double cast is
  required because better-sqlite3 returns Record<string, SQLOutputValue>).
- rowToX(row: any) converters: typed argument with the matching row interface.
- Dynamic settings key writes: (settings as Record<string, unknown>)[key].
- React event handlers and setState callbacks: inferred types or concrete
  React.{Mouse,Change,Form}Event<...> where needed.
- pi-claude-cli: local PiMessage / PiContext duck types to avoid re-typing
  pi-ai concrete shapes; typed Claude stream event message fields.

72 files changed, ~400 anys eliminated. Typecheck passes across the workspace.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-04-23 18:57:31 -07:00
parent d1bd02b2c9
commit 4cabe7f613
72 changed files with 1298 additions and 783 deletions

View File

@@ -192,9 +192,11 @@ export function withTokenHeader(init?: HeadersInit): HeadersInit | undefined {
* through the `api()` helper without requiring us to touch each one.
*/
export function installAuthFetch(): void {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- property sentinel on global window object, no better type exists
if (typeof window === "undefined" || (window as any).__fnAuthFetchInstalled) {
return;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- property sentinel on global window object, no better type exists
(window as any).__fnAuthFetchInstalled = true;
// Ensure token is captured-from-URL before the first fetch fires.

View File

@@ -7,10 +7,11 @@ import {
} from "lucide-react";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import type { AgentDetail, AgentState, AgentHeartbeatRun, AgentBudgetStatus, ModelInfo, MemoryFileInfo } from "../api";
import type { AgentDetail, AgentState, AgentHeartbeatRun, AgentBudgetStatus, ModelInfo, MemoryFileInfo, AgentCapability } from "../api";
import { fetchAgent, updateAgent, updateAgentState, deleteAgent, fetchAgentLogsWithMeta, fetchAgentRunLogs, fetchAgentChildren, fetchAgentRuns, fetchAgentRunDetail, startAgentRun, stopAgentRun, updateAgentInstructions, updateAgentSoul, updateAgentMemory, fetchAgentMemoryFiles, fetchAgentMemoryFile, saveAgentMemoryFile, fetchAgentTasks, fetchChainOfCommand, fetchAgentBudgetStatus, resetAgentBudget, fetchWorkspaceFileContent, saveWorkspaceFileContent, fetchModels, fetchAgents } from "../api";
import type { Agent } from "../api";
import type { AgentLogEntry, Task } from "@fusion/core";
import { getErrorMessage } from "@fusion/core";
import { AgentLogViewer } from "./AgentLogViewer";
import { AgentReflectionsTab } from "./AgentReflectionsTab";
import { getAgentHealthStatus } from "../utils/agentHealth";
@@ -146,8 +147,8 @@ export function AgentDetailView({ agentId, projectId, onClose, addToast, onChild
try {
const data = await fetchAgent(agentId, projectId);
setAgent(data);
} catch (err: any) {
addToastRef.current(`Failed to load agent: ${err.message}`, "error");
} catch (err) {
addToastRef.current(`Failed to load agent: ${getErrorMessage(err)}`, "error");
onCloseRef.current();
} finally {
setIsLoading(false);
@@ -174,7 +175,7 @@ export function AgentDetailView({ agentId, projectId, onClose, addToast, onChild
return;
}
setLogs(result.entries);
} catch (err: any) {
} catch (err) {
// Reject stale error: check context version and current IDs
if (contextVersionRef.current !== contextVersionAtCapture ||
agentId !== currentAgentId ||
@@ -310,8 +311,8 @@ export function AgentDetailView({ agentId, projectId, onClose, addToast, onChild
await updateAgentState(agentId, newState, projectId);
addToast(`Agent state updated to ${newState}`, "success");
void loadAgent();
} catch (err: any) {
addToast(`Failed to update state: ${err.message}`, "error");
} catch (err) {
addToast(`Failed to update state: ${getErrorMessage(err)}`, "error");
}
};
@@ -321,8 +322,8 @@ export function AgentDetailView({ agentId, projectId, onClose, addToast, onChild
await deleteAgent(agentId, projectId);
addToast(`Agent "${agent.name}" deleted`, "success");
onClose();
} catch (err: any) {
addToast(`Failed to delete agent: ${err.message}`, "error");
} catch (err) {
addToast(`Failed to delete agent: ${getErrorMessage(err)}`, "error");
}
};
@@ -674,7 +675,7 @@ function DashboardTab({
}, [agent.id, projectId]);
const stats = useMemo(() => {
const runs = (agent as any).completedRuns || [];
const runs = agent.completedRuns || [];
const today = new Date();
today.setHours(0, 0, 0, 0);
@@ -1049,8 +1050,8 @@ function RunsTab({
try {
const data = await fetchAgentRuns(agentId, 50, projectId);
setRuns(data);
} catch (err: any) {
addToast(`Failed to load runs: ${err.message}`, "error");
} catch (err) {
addToast(`Failed to load runs: ${getErrorMessage(err)}`, "error");
} finally {
setIsLoadingRuns(false);
}
@@ -1090,8 +1091,8 @@ function RunsTab({
]);
setRunLogs(logs);
setDetailRun(detail);
} catch (err: any) {
addToast(`Failed to load run details: ${err.message}`, "error");
} catch (err) {
addToast(`Failed to load run details: ${getErrorMessage(err)}`, "error");
setRunLogs([]);
setDetailRun(null);
} finally {
@@ -1106,8 +1107,8 @@ function RunsTab({
addToast(`Heartbeat run started for ${agentName ?? agentId}`, "success");
setIsLoadingRuns(true);
void loadRuns();
} catch (err: any) {
addToast(`Failed to start heartbeat run: ${err.message}`, "error");
} catch (err) {
addToast(`Failed to start heartbeat run: ${getErrorMessage(err)}`, "error");
}
};
@@ -1121,8 +1122,8 @@ function RunsTab({
addToast("Run stopped", "success");
setIsLoadingRuns(true);
void loadRuns();
} catch (err: any) {
addToast(`Failed to stop run: ${err.message}`, "error");
} catch (err) {
addToast(`Failed to stop run: ${getErrorMessage(err)}`, "error");
}
};
@@ -1479,10 +1480,10 @@ function TasksTab({
setTasks(assignedTasks);
}
})
.catch((err: any) => {
.catch((err) => {
if (!cancelled) {
setTasks([]);
addToast(`Failed to load assigned tasks: ${err.message}`, "error");
addToast(`Failed to load assigned tasks: ${getErrorMessage(err)}`, "error");
}
})
.finally(() => {
@@ -1660,8 +1661,8 @@ function SoulTab({
setJustSaved(true);
setTimeout(() => setJustSaved(false), 3000);
await onSaved();
} catch (err: any) {
addToast(`Failed to save soul: ${err.message}`, "error");
} catch (err) {
addToast(`Failed to save soul: ${getErrorMessage(err)}`, "error");
} finally {
setIsSaving(false);
}
@@ -1810,8 +1811,8 @@ function MemoryTab({
setSelectedFileContent(result.content);
setSelectedFileDirty(false);
setSelectedFileJustSaved(false);
} catch (err: any) {
addToast(`Failed to load agent memory file: ${err.message}`, "error");
} catch (err) {
addToast(`Failed to load agent memory file: ${getErrorMessage(err)}`, "error");
} finally {
setSelectedFileLoading(false);
}
@@ -1832,8 +1833,8 @@ function MemoryTab({
const nextPath = pickDefaultAgentMemoryPath(files, preferredPath);
await loadSelectedMemoryFile(nextPath);
} catch (err: any) {
addToast(`Failed to load memory files: ${err.message}`, "error");
} catch (err) {
addToast(`Failed to load memory files: ${getErrorMessage(err)}`, "error");
setMemoryFiles([]);
setSelectedFilePath("");
setSelectedFileContent("");
@@ -1865,8 +1866,8 @@ function MemoryTab({
setJustSaved(true);
setTimeout(() => setJustSaved(false), 3000);
await onSaved();
} catch (err: any) {
addToast(`Failed to save memory: ${err.message}`, "error");
} catch (err) {
addToast(`Failed to save memory: ${getErrorMessage(err)}`, "error");
} finally {
setIsSaving(false);
}
@@ -1899,8 +1900,8 @@ function MemoryTab({
setFileSwitchHint("");
await loadMemoryFiles(selectedFilePath);
addToast("Agent memory file saved", "success");
} catch (err: any) {
addToast(`Failed to save agent memory file: ${err.message}`, "error");
} catch (err) {
addToast(`Failed to save agent memory file: ${getErrorMessage(err)}`, "error");
} finally {
setSavingSelectedFile(false);
}
@@ -2150,13 +2151,14 @@ function InstructionsTab({
setFileContent(data.content);
setFileContentDirty(false);
})
.catch((err: any) => {
.catch((err) => {
// ENOENT means file doesn't exist yet - treat as empty "new file" state
if (err.message?.includes("ENOENT") || err.message?.includes("Not found") || err.message?.includes("not found")) {
const msg = getErrorMessage(err);
if (msg.includes("ENOENT") || msg.includes("Not found") || msg.includes("not found")) {
setFileContent("");
setFileContentDirty(false);
} else {
addToast(`Failed to load instructions file: ${err.message}`, "error");
addToast(`Failed to load instructions file: ${msg}`, "error");
setFileContent("");
}
})
@@ -2197,8 +2199,8 @@ function InstructionsTab({
setJustSaved(true);
setTimeout(() => setJustSaved(false), 3000);
await onSaved();
} catch (err: any) {
addToast(`Failed to save instructions: ${err.message}`, "error");
} catch (err) {
addToast(`Failed to save instructions: ${getErrorMessage(err)}`, "error");
} finally {
setIsSaving(false);
}
@@ -2218,8 +2220,8 @@ function InstructionsTab({
setFileContentDirty(false);
setJustSavedFile(true);
setTimeout(() => setJustSavedFile(false), 3000);
} catch (err: any) {
addToast(`Failed to save instructions file: ${err.message}`, "error");
} catch (err) {
addToast(`Failed to save instructions file: ${getErrorMessage(err)}`, "error");
} finally {
setIsSavingFile(false);
}
@@ -2437,8 +2439,8 @@ function PerformanceTab({
]);
setSummary(summaryData);
setRatings(ratingsData);
} catch (err: any) {
addToast(`Failed to load ratings: ${err.message}`, "error");
} catch (err) {
addToast(`Failed to load ratings: ${getErrorMessage(err)}`, "error");
} finally {
setLoading(false);
}
@@ -2466,8 +2468,8 @@ function PerformanceTab({
setNewComment("");
addToast("Rating added", "success");
await loadData();
} catch (err: any) {
addToast(`Failed to add rating: ${err.message}`, "error");
} catch (err) {
addToast(`Failed to add rating: ${getErrorMessage(err)}`, "error");
} finally {
setSubmitting(false);
}
@@ -2479,8 +2481,8 @@ function PerformanceTab({
await deleteAgentRating(agentId, ratingId, projectId);
addToast("Rating deleted", "success");
await loadData();
} catch (err: any) {
addToast(`Failed to delete rating: ${err.message}`, "error");
} catch (err) {
addToast(`Failed to delete rating: ${getErrorMessage(err)}`, "error");
}
};
@@ -2824,8 +2826,8 @@ function ConfigTab({
// Refresh budget status
const status = await fetchAgentBudgetStatus(agent.id, projectId);
setBudgetStatus(status);
} catch (err: any) {
addToast(`Failed to reset budget: ${err.message}`, "error");
} catch (err) {
addToast(`Failed to reset budget: ${getErrorMessage(err)}`, "error");
} finally {
setIsResettingBudget(false);
}
@@ -3160,7 +3162,7 @@ function ConfigTab({
try {
await updateAgent(agent.id, {
name: nameValue.trim() || undefined,
role: roleValue as any,
role: roleValue,
title: titleValue.trim() || undefined,
icon: iconValue.trim() || undefined,
reportsTo: reportsToValue.trim() || undefined,
@@ -3176,8 +3178,8 @@ function ConfigTab({
}
justSavedTimeoutRef.current = setTimeout(() => setJustSaved(false), 3000);
await onSaved();
} catch (err: any) {
addToast(`Failed to save settings: ${err.message}`, "error");
} catch (err) {
addToast(`Failed to save settings: ${getErrorMessage(err)}`, "error");
} finally {
setIsSaving(false);
}
@@ -3209,7 +3211,7 @@ function ConfigTab({
id="agent-role"
className="select"
value={roleValue}
onChange={(e) => setRoleValue(e.target.value as any)}
onChange={(e) => setRoleValue(e.target.value as AgentCapability)}
>
<option value="triage">Triage</option>
<option value="executor">Executor</option>

View File

@@ -4,6 +4,7 @@ import type { Agent, AgentCapability, AgentState } from "../api";
import { fetchAgents, createAgent, updateAgent, updateAgentState, deleteAgent } from "../api";
import { getScopedItem, setScopedItem } from "../utils/projectStorage";
import { getAgentHealthStatus } from "../utils/agentHealth";
import { getErrorMessage } from "@fusion/core";
import type { AgentHealthStatus } from "../utils/agentHealth";
interface AgentListModalProps {
@@ -68,8 +69,8 @@ export function AgentListModal({ isOpen, onClose, addToast, projectId }: AgentLi
const filter = filterState !== "all" ? { state: filterState } : undefined;
const data = await fetchAgents(filter, projectId);
setAgents(data);
} catch (err: any) {
addToast(`Failed to load agents: ${err.message}`, "error");
} catch (err) {
addToast(`Failed to load agents: ${getErrorMessage(err)}`, "error");
} finally {
setIsLoading(false);
}
@@ -103,8 +104,8 @@ export function AgentListModal({ isOpen, onClose, addToast, projectId }: AgentLi
setNewAgentName("");
setIsCreating(false);
void loadAgents();
} catch (err: any) {
addToast(`Failed to create agent: ${err.message}`, "error");
} catch (err) {
addToast(`Failed to create agent: ${getErrorMessage(err)}`, "error");
}
};
@@ -113,8 +114,8 @@ export function AgentListModal({ isOpen, onClose, addToast, projectId }: AgentLi
await updateAgentState(agentId, newState, projectId);
addToast(`Agent state updated to ${newState}`, "success");
void loadAgents();
} catch (err: any) {
addToast(`Failed to update state: ${err.message}`, "error");
} catch (err) {
addToast(`Failed to update state: ${getErrorMessage(err)}`, "error");
}
};
@@ -124,8 +125,8 @@ export function AgentListModal({ isOpen, onClose, addToast, projectId }: AgentLi
await deleteAgent(agentId, projectId);
addToast(`Agent "${agentName}" deleted`, "success");
void loadAgents();
} catch (err: any) {
addToast(`Failed to delete agent: ${err.message}`, "error");
} catch (err) {
addToast(`Failed to delete agent: ${getErrorMessage(err)}`, "error");
}
};
@@ -144,8 +145,8 @@ export function AgentListModal({ isOpen, onClose, addToast, projectId }: AgentLi
addToast(`Agent role updated to ${AGENT_ROLES.find(r => r.value === newRole)?.label ?? newRole}`, "success");
setEditingRoleForAgent(null);
void loadAgents();
} catch (err: any) {
addToast(`Failed to update role: ${err.message}`, "error");
} catch (err) {
addToast(`Failed to update role: ${getErrorMessage(err)}`, "error");
}
};

View File

@@ -95,8 +95,8 @@ export function AgentReflectionsTab({ agentId, projectId, addToast }: AgentRefle
]);
setReflections(reflectionsData);
setPerformance(performanceData);
} catch (err: any) {
addToast(`Failed to load reflections: ${err.message}`, "error");
} catch (err) {
addToast(`Failed to load reflections: ${getErrorMessage(err)}`, "error");
} finally {
setIsLoading(false);
}

View File

@@ -22,7 +22,7 @@ import {
MIN_HEARTBEAT_INTERVAL_MS,
HEARTBEAT_INTERVAL_PRESETS,
} from "../utils/heartbeatIntervals";
import { isEphemeralAgent } from "@fusion/core";
import { isEphemeralAgent, getErrorMessage } from "@fusion/core";
export interface AgentsViewProps {
addToast: (message: string, type?: "success" | "error") => void;
@@ -313,8 +313,8 @@ export function AgentsView({ addToast, projectId }: AgentsViewProps) {
try {
await updateSettings({ heartbeatMultiplier: clampedValue }, projectId);
addToast(`Heartbeat speed set to ×${clampedValue.toFixed(1)}`, "success");
} catch (err: any) {
addToast(`Failed to save heartbeat multiplier: ${err.message}`, "error");
} catch (err) {
addToast(`Failed to save heartbeat multiplier: ${getErrorMessage(err)}`, "error");
} finally {
setIsSavingMultiplier(false);
}
@@ -356,8 +356,8 @@ export function AgentsView({ addToast, projectId }: AgentsViewProps) {
const filter = filterState !== "all" ? { state: filterState } : undefined;
const data = await fetchAgents({ ...filter, includeEphemeral: showSystemAgents }, projectId);
setAgents(data);
} catch (err: any) {
addToast(`Failed to load agents: ${err.message}`, "error");
} catch (err) {
addToast(`Failed to load agents: ${getErrorMessage(err)}`, "error");
} finally {
setIsLoading(false);
}
@@ -378,9 +378,9 @@ export function AgentsView({ addToast, projectId }: AgentsViewProps) {
setOrgTree(data);
}
})
.catch((err: any) => {
.catch((err) => {
if (!cancelled) {
addToast(`Failed to load org chart: ${err.message}`, "error");
addToast(`Failed to load org chart: ${getErrorMessage(err)}`, "error");
setOrgTree([]);
}
})
@@ -435,16 +435,16 @@ export function AgentsView({ addToast, projectId }: AgentsViewProps) {
if (newState === "active") {
try {
await startAgentRun(agentId, projectId);
} catch (runErr: any) {
addToast(`Agent activated, but failed to start run: ${runErr.message}`, "error");
} catch (runErr) {
addToast(`Agent activated, but failed to start run: ${getErrorMessage(runErr)}`, "error");
}
}
void loadAgents();
} catch (err: any) {
} catch (err) {
if (previousAgent) {
setAgents(prev => prev.map(a => a.id === agentId ? previousAgent : a));
}
addToast(`Failed to update state: ${err.message}`, "error");
addToast(`Failed to update state: ${getErrorMessage(err)}`, "error");
} finally {
setTransitioningAgentIds(prev => { const next = new Set(prev); next.delete(agentId); return next; });
}
@@ -456,8 +456,8 @@ export function AgentsView({ addToast, projectId }: AgentsViewProps) {
await deleteAgent(agentId, projectId);
addToast(`Agent "${agentName}" deleted`, "success");
void loadAgents();
} catch (err: any) {
addToast(`Failed to delete agent: ${err.message}`, "error");
} catch (err) {
addToast(`Failed to delete agent: ${getErrorMessage(err)}`, "error");
}
};
@@ -476,8 +476,8 @@ export function AgentsView({ addToast, projectId }: AgentsViewProps) {
addToast(`Agent role updated to ${AGENT_ROLES.find(r => r.value === newRole)?.label ?? newRole}`, "success");
setEditingRoleForAgent(null);
void loadAgents();
} catch (err: any) {
addToast(`Failed to update role: ${err.message}`, "error");
} catch (err) {
addToast(`Failed to update role: ${getErrorMessage(err)}`, "error");
}
};
@@ -512,8 +512,8 @@ export function AgentsView({ addToast, projectId }: AgentsViewProps) {
);
addToast(`Heartbeat interval updated to ${formatHeartbeatInterval(newIntervalMs)} for ${agent.name}`, "success");
void loadAgents();
} catch (err: any) {
addToast(`Failed to update heartbeat interval: ${err.message}`, "error");
} catch (err) {
addToast(`Failed to update heartbeat interval: ${getErrorMessage(err)}`, "error");
} finally {
setUpdatingHeartbeatAgentId(null);
}
@@ -572,8 +572,8 @@ export function AgentsView({ addToast, projectId }: AgentsViewProps) {
return next;
});
void loadAgents();
} catch (err: any) {
addToast(`Failed to update heartbeat interval: ${err.message}`, "error");
} catch (err) {
addToast(`Failed to update heartbeat interval: ${getErrorMessage(err)}`, "error");
} finally {
setUpdatingHeartbeatAgentId(null);
}
@@ -602,8 +602,8 @@ export function AgentsView({ addToast, projectId }: AgentsViewProps) {
return next;
});
void loadAgents();
} catch (err: any) {
addToast(`Failed to update heartbeat interval: ${err.message}`, "error");
} catch (err) {
addToast(`Failed to update heartbeat interval: ${getErrorMessage(err)}`, "error");
} finally {
setUpdatingHeartbeatAgentId(null);
}
@@ -634,8 +634,8 @@ export function AgentsView({ addToast, projectId }: AgentsViewProps) {
await startAgentRun(agentId, projectId, { source: "on_demand", triggerDetail: "Triggered from dashboard" });
addToast(`Heartbeat run started for ${agentName}`, "success");
void loadAgents();
} catch (err: any) {
addToast(`Failed to start heartbeat run: ${err.message}`, "error");
} catch (err) {
addToast(`Failed to start heartbeat run: ${getErrorMessage(err)}`, "error");
}
};

View File

@@ -1,7 +1,7 @@
import { memo, useMemo, useState, useCallback, useEffect } from "react";
import { useFlashOnIncrease } from "../hooks/useFlashOnIncrease";
import type { Task, TaskDetail, Column as ColumnType, TaskCreateInput } from "@fusion/core";
import { COLUMN_LABELS, COLUMN_DESCRIPTIONS } from "@fusion/core";
import { COLUMN_LABELS, COLUMN_DESCRIPTIONS, getErrorMessage } from "@fusion/core";
import { TaskCard } from "./TaskCard";
import { WorktreeGroup } from "./WorktreeGroup";
import { QuickEntryBox } from "./QuickEntryBox";
@@ -113,8 +113,8 @@ function ColumnComponent({ column, tasks, projectId, maxConcurrent, onMoveTask,
try {
await onMoveTask(taskId, column);
} catch (err: any) {
addToast(err.message, "error");
} catch (err) {
addToast(getErrorMessage(err), "error");
}
}, [column, onMoveTask, addToast, tasks]);
@@ -144,8 +144,8 @@ function ColumnComponent({ column, tasks, projectId, maxConcurrent, onMoveTask,
try {
const archived = await onArchiveAllDone();
addToast(`Archived ${archived.length} tasks`, "success");
} catch (err: any) {
addToast(err.message || "Failed to archive tasks", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to archive tasks", "error");
}
}, [onArchiveAllDone, tasks.length, addToast]);

View File

@@ -2,6 +2,7 @@ import { useState, useEffect, useCallback } from "react";
import { FileCode, ChevronDown, ChevronRight, AlertCircle, GitCommit } from "lucide-react";
import type { MergeDetails } from "@fusion/core";
import { fetchCommitDiff } from "../api";
import { getErrorMessage } from "@fusion/core";
import { highlightDiff } from "../utils/highlightDiff";
interface CommitDiffTabProps {
@@ -92,8 +93,8 @@ export function CommitDiffTab({ commitSha, mergeDetails }: CommitDiffTabProps) {
if (parsed.length > 0) {
setExpandedFiles(new Set([parsed[0].path]));
}
} catch (err: any) {
setError(err.message || "Failed to load commit diff");
} catch (err) {
setError(getErrorMessage(err) || "Failed to load commit diff");
} finally {
setLoading(false);
}

View File

@@ -3,6 +3,7 @@ import { Folder, File, ChevronRight, Loader2, Copy, Move, Trash2, Pencil, Downlo
import type { FileNode } from "../api";
import { copyFile, moveFile, deleteFile, renameFile, downloadFileUrl, downloadZipUrl } from "../api";
import { appendTokenQuery } from "../auth";
import { getErrorMessage } from "@fusion/core";
interface FileBrowserProps {
entries: FileNode[];
@@ -492,8 +493,8 @@ export function FileBrowser({
setDialog(INITIAL_DIALOG);
onRefresh?.();
} catch (err: any) {
setOperationError(err.message || "Operation failed");
} catch (err) {
setOperationError(getErrorMessage(err) || "Operation failed");
} finally {
setOperationLoading(false);
}

View File

@@ -1,5 +1,6 @@
import { useState, useEffect, useCallback, useRef } from "react";
import type { Task } from "@fusion/core";
import { getErrorMessage } from "@fusion/core";
import {
apiFetchGitHubIssues,
apiImportGitHubIssue,
@@ -164,8 +165,8 @@ export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId
if (fetchedIssues.length === 0) {
setError("No open issues found");
}
} catch (err: any) {
setError(err.message || "Failed to fetch issues");
} catch (err) {
setError(getErrorMessage(err) || "Failed to fetch issues");
} finally {
setLoading(false);
}
@@ -189,8 +190,8 @@ export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId
if (fetchedPulls.length === 0) {
setError("No open pull requests found");
}
} catch (err: any) {
setError(err.message || "Failed to fetch pull requests");
} catch (err) {
setError(getErrorMessage(err) || "Failed to fetch pull requests");
} finally {
setLoading(false);
}
@@ -280,11 +281,12 @@ export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId
const task = await apiImportGitHubIssue(owner.trim(), repo.trim(), selectedIssueNumber, projectId);
onImport(task);
onClose();
} catch (err: any) {
if (err.message?.includes("already imported")) {
setError(err.message);
} catch (err) {
const msg = getErrorMessage(err);
if (msg?.includes("already imported")) {
setError(msg);
} else {
setError(err.message || "Failed to import issue");
setError(msg || "Failed to import issue");
}
} finally {
setImporting(false);
@@ -299,11 +301,12 @@ export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId
const task = await apiImportGitHubPull(owner.trim(), repo.trim(), selectedPullNumber, projectId);
onImport(task);
onClose();
} catch (err: any) {
if (err.message?.includes("already imported")) {
setError(err.message);
} catch (err) {
const msg = getErrorMessage(err);
if (msg?.includes("already imported")) {
setError(msg);
} else {
setError(err.message || "Failed to import pull request");
setError(msg || "Failed to import pull request");
}
} finally {
setImporting(false);

View File

@@ -1,5 +1,6 @@
import { useState, useEffect, useCallback, useRef, useMemo } from "react";
import type { Task } from "@fusion/core";
import { getErrorMessage } from "@fusion/core";
import type { ToastType } from "../hooks/useToast";
import type {
GitStatus,
@@ -265,9 +266,9 @@ export function GitManagerModal({ isOpen, onClose, tasks: _tasks, addToast, proj
break;
}
}
} catch (err: any) {
setSectionError(err.message || "Failed to fetch git data");
addToast(err.message || "Failed to fetch git data", "error");
} catch (err) {
setSectionError(getErrorMessage(err) || "Failed to fetch git data");
addToast(getErrorMessage(err) || "Failed to fetch git data", "error");
} finally {
setLoading(false);
}
@@ -312,8 +313,8 @@ export function GitManagerModal({ isOpen, onClose, tasks: _tasks, addToast, proj
const changes = await fetchFileChanges(projectId);
setFileChanges(changes);
setSelectedFiles(new Set());
} catch (err: any) {
addToast(err.message || "Failed to stage files", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to stage files", "error");
}
}, [addToast, projectId]);
@@ -324,8 +325,8 @@ export function GitManagerModal({ isOpen, onClose, tasks: _tasks, addToast, proj
const changes = await fetchFileChanges(projectId);
setFileChanges(changes);
setSelectedFiles(new Set());
} catch (err: any) {
addToast(err.message || "Failed to unstage files", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to unstage files", "error");
}
}, [addToast, projectId]);
@@ -338,8 +339,8 @@ export function GitManagerModal({ isOpen, onClose, tasks: _tasks, addToast, proj
setFileChanges(changes);
setStatus(statusData);
setSelectedFiles(new Set());
} catch (err: any) {
addToast(err.message || "Failed to discard changes", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to discard changes", "error");
}
}, [addToast, projectId]);
@@ -355,8 +356,8 @@ export function GitManagerModal({ isOpen, onClose, tasks: _tasks, addToast, proj
const [changes, statusData] = await Promise.all([fetchFileChanges(projectId), fetchGitStatus(projectId)]);
setFileChanges(changes);
setStatus(statusData);
} catch (err: any) {
addToast(err.message || "Failed to commit", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to commit", "error");
} finally {
setCommitting(false);
}
@@ -376,8 +377,8 @@ export function GitManagerModal({ isOpen, onClose, tasks: _tasks, addToast, proj
const [changes, statusData] = await Promise.all([fetchFileChanges(projectId), fetchGitStatus(projectId)]);
setFileChanges(changes);
setStatus(statusData);
} catch (err: any) {
addToast(err.message || "Failed to commit", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to commit", "error");
} finally {
setCommitting(false);
}
@@ -388,8 +389,8 @@ export function GitManagerModal({ isOpen, onClose, tasks: _tasks, addToast, proj
try {
const diff = await fetchUnstagedDiff(projectId);
setChangeDiff(diff);
} catch (err: any) {
addToast(err.message || "Failed to load diff", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to load diff", "error");
} finally {
setLoadingChangeDiff(false);
}
@@ -420,8 +421,8 @@ export function GitManagerModal({ isOpen, onClose, tasks: _tasks, addToast, proj
try {
const diff = await fetchCommitDiff(hash, projectId);
setCommitDiff(diff);
} catch (err: any) {
addToast(err.message || "Failed to load diff", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to load diff", "error");
setCommitDiff(null);
} finally {
setLoadingDiff(false);
@@ -456,8 +457,8 @@ export function GitManagerModal({ isOpen, onClose, tasks: _tasks, addToast, proj
setBranchBase("");
const branchesData = await fetchGitBranches(projectId);
setBranches(branchesData);
} catch (err: any) {
addToast(err.message || "Failed to create branch", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to create branch", "error");
} finally {
setLoading(false);
}
@@ -471,8 +472,8 @@ export function GitManagerModal({ isOpen, onClose, tasks: _tasks, addToast, proj
const [statusData, branchesData] = await Promise.all([fetchGitStatus(projectId), fetchGitBranches(projectId)]);
setStatus(statusData);
setBranches(branchesData);
} catch (err: any) {
addToast(err.message || "Failed to checkout branch", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to checkout branch", "error");
} finally {
setLoading(false);
}
@@ -486,20 +487,20 @@ export function GitManagerModal({ isOpen, onClose, tasks: _tasks, addToast, proj
addToast(`Deleted branch ${name}`, "success");
const branchesData = await fetchGitBranches(projectId);
setBranches(branchesData);
} catch (err: any) {
if (err.message?.includes("not fully merged")) {
} catch (err) {
if (getErrorMessage(err).includes("not fully merged")) {
if (confirm("Branch has unmerged commits. Force delete?")) {
try {
await deleteBranch(name, true, projectId);
addToast(`Force deleted branch ${name}`, "success");
const branchesData = await fetchGitBranches(projectId);
setBranches(branchesData);
} catch (forceErr: any) {
addToast(forceErr.message || "Failed to delete branch", "error");
} catch (forceErr) {
addToast(getErrorMessage(forceErr) || "Failed to delete branch", "error");
}
}
} else {
addToast(err.message || "Failed to delete branch", "error");
addToast(getErrorMessage(err) || "Failed to delete branch", "error");
}
} finally {
setLoading(false);
@@ -578,8 +579,8 @@ export function GitManagerModal({ isOpen, onClose, tasks: _tasks, addToast, proj
setStashMessage("");
const stashesData = await fetchGitStashList(projectId);
setStashes(stashesData);
} catch (err: any) {
addToast(err.message || "Failed to stash changes", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to stash changes", "error");
} finally {
setStashLoading(null);
}
@@ -592,8 +593,8 @@ export function GitManagerModal({ isOpen, onClose, tasks: _tasks, addToast, proj
addToast(drop ? "Stash popped" : "Stash applied", "success");
const stashesData = await fetchGitStashList(projectId);
setStashes(stashesData);
} catch (err: any) {
addToast(err.message || "Failed to apply stash", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to apply stash", "error");
} finally {
setStashLoading(null);
}
@@ -607,8 +608,8 @@ export function GitManagerModal({ isOpen, onClose, tasks: _tasks, addToast, proj
addToast("Stash dropped", "success");
const stashesData = await fetchGitStashList(projectId);
setStashes(stashesData);
} catch (err: any) {
addToast(err.message || "Failed to drop stash", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to drop stash", "error");
} finally {
setStashLoading(null);
}
@@ -624,8 +625,8 @@ export function GitManagerModal({ isOpen, onClose, tasks: _tasks, addToast, proj
addToast(result.message || "Fetch completed", result.fetched ? "success" : "info");
const statusData = await fetchGitStatus(projectId);
setStatus(statusData);
} catch (err: any) {
addToast(err.message || "Fetch failed", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Fetch failed", "error");
} finally {
setRemoteLoading(null);
}
@@ -643,8 +644,8 @@ export function GitManagerModal({ isOpen, onClose, tasks: _tasks, addToast, proj
}
const statusData = await fetchGitStatus(projectId);
setStatus(statusData);
} catch (err: any) {
addToast(err.message || "Pull failed", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Pull failed", "error");
} finally {
setRemoteLoading(null);
}
@@ -658,8 +659,8 @@ export function GitManagerModal({ isOpen, onClose, tasks: _tasks, addToast, proj
addToast(result.message || "Push completed", "success");
const statusData = await fetchGitStatus(projectId);
setStatus(statusData);
} catch (err: any) {
addToast(err.message || "Push failed", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Push failed", "error");
} finally {
setRemoteLoading(null);
}
@@ -1747,8 +1748,8 @@ function RemotesPanel({
try {
const data = await fetchGitRemotesDetailed(projectId);
setRemotes(data);
} catch (err: any) {
addToast(err.message || "Failed to load remotes", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to load remotes", "error");
} finally {
setLoading(false);
}
@@ -1773,8 +1774,8 @@ function RemotesPanel({
try {
const commits = await fetchRemoteCommits(remoteName, undefined, 10, projectId);
setRemoteCommits(commits);
} catch (err: any) {
setRemoteCommitsError(err.message || "Failed to load remote commits");
} catch (err) {
setRemoteCommitsError(getErrorMessage(err) || "Failed to load remote commits");
setRemoteCommits([]);
} finally {
setLoadingRemoteCommits(false);
@@ -1793,8 +1794,8 @@ function RemotesPanel({
setNewRemoteUrl("");
setShowAddForm(false);
await loadRemotes();
} catch (err: any) {
addToast(err.message || "Failed to add remote", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to add remote", "error");
} finally {
setRemoteActionLoading(null);
}
@@ -1808,8 +1809,8 @@ function RemotesPanel({
await removeGitRemote(name, projectId);
addToast(`Remote '${name}' removed`, "success");
await loadRemotes();
} catch (err: any) {
addToast(err.message || "Failed to remove remote", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to remove remote", "error");
} finally {
setRemoteActionLoading(null);
}
@@ -1825,8 +1826,8 @@ function RemotesPanel({
setEditingRemote(null);
setEditNameValue("");
await loadRemotes();
} catch (err: any) {
addToast(err.message || "Failed to rename remote", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to rename remote", "error");
} finally {
setRemoteActionLoading(null);
}
@@ -1842,8 +1843,8 @@ function RemotesPanel({
setEditingRemote(null);
setEditUrlValue("");
await loadRemotes();
} catch (err: any) {
addToast(err.message || "Failed to update remote URL", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to update remote URL", "error");
} finally {
setRemoteActionLoading(null);
}

View File

@@ -2,6 +2,7 @@ import { useState, useCallback, useEffect, useRef } from "react";
import { createPortal } from "react-dom";
import { Brain, Link, Lightbulb, ListTree, Zap, ChevronDown, ChevronUp, Bot, Maximize2, Minimize2 } from "lucide-react";
import type { Task, TaskCreateInput, Settings } from "@fusion/core";
import { getErrorMessage } from "@fusion/core";
import type { ToastType } from "../hooks/useToast";
import { fetchModels, uploadAttachment, fetchSettings, updateGlobalSettings, fetchAgents } from "../api";
import type { ModelInfo, Agent } from "../api";
@@ -140,8 +141,8 @@ export function InlineCreateCard({
setLoadedModels(response.models);
setFavoriteProviders(response.favoriteProviders);
setFavoriteModels(response.favoriteModels);
} catch (err: any) {
setModelsError(err?.message || "Failed to load models");
} catch (err) {
setModelsError(getErrorMessage(err) || "Failed to load models");
} finally {
setModelsLoading(false);
}
@@ -362,8 +363,8 @@ export function InlineCreateCard({
if (typeof window !== "undefined") {
removeScopedItem(STORAGE_KEY, projectId);
}
} catch (err: any) {
addToast(err.message, "error");
} catch (err) {
addToast(getErrorMessage(err), "error");
} finally {
setSubmitting(false);
}
@@ -472,8 +473,9 @@ export function InlineCreateCard({
const result = await fetchAgents(undefined, projectId);
setAgents(result);
setShowAgentPicker(true);
} catch (err: any) {
addToast(err?.message ? `Failed to load agents: ${err.message}` : "Failed to load agents", "error");
} catch (err) {
const msg = getErrorMessage(err);
addToast(msg ? `Failed to load agents: ${msg}` : "Failed to load agents", "error");
setShowAgentPicker(false);
} finally {
setAgentsLoading(false);

View File

@@ -1,7 +1,7 @@
import { useState, useCallback, useMemo, Fragment, useEffect, useRef } from "react";
import { ArrowUpDown, ArrowUp, ArrowDown, Link, Columns3, EyeOff, Eye, ChevronRight } from "lucide-react";
import type { Task, TaskDetail, Column, TaskCreateInput } from "@fusion/core";
import { COLUMN_LABELS, COLUMNS } from "@fusion/core";
import { COLUMN_LABELS, COLUMNS, getErrorMessage } from "@fusion/core";
import { batchUpdateTaskModels } from "../api";
import type { ModelInfo } from "../api";
import { QuickEntryBox } from "./QuickEntryBox";
@@ -522,8 +522,8 @@ export function ListView({
clearSelection();
setExecutorModel("__no_change__");
setValidatorModel("__no_change__");
} catch (err: any) {
addToast(err.message || "Failed to update models", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to update models", "error");
} finally {
setIsApplying(false);
}
@@ -582,8 +582,8 @@ export function ListView({
try {
await onMoveTask(taskId, column);
} catch (err: any) {
addToast(err.message, "error");
} catch (err) {
addToast(getErrorMessage(err), "error");
}
},
[onMoveTask, addToast]

View File

@@ -1,6 +1,7 @@
import { useState, useCallback } from "react";
import { X, Send, Loader2, Bot, AlertCircle } from "lucide-react";
import type { ParticipantType, MessageType } from "@fusion/core";
import { getErrorMessage } from "@fusion/core";
import { sendMessage } from "../api";
import type { Agent } from "../api";
@@ -66,8 +67,8 @@ export function MessageComposer({
projectId,
);
onSend();
} catch (err: any) {
const msg = err?.message ?? "Failed to send message";
} catch (err) {
const msg = getErrorMessage(err) || "Failed to send message";
setError(msg);
addToast?.(msg, "error");
} finally {

View File

@@ -1,5 +1,6 @@
import { useState, useCallback, useEffect, useMemo, useRef } from "react";
import type { PlanningQuestion } from "@fusion/core";
import { getErrorMessage } from "@fusion/core";
import {
startMilestoneInterview,
startSliceInterview,
@@ -210,9 +211,9 @@ export function MilestoneSliceInterviewModal({
currentSessionIdRef.current = sessionId;
setLockSessionId(sessionId);
connectToInterviewStream(sessionId);
} catch (err: any) {
} catch (err) {
setIsReconnecting(false);
setError(err.message || `Failed to start ${targetLabel.toLowerCase()} interview`);
setError(getErrorMessage(err) || `Failed to start ${targetLabel.toLowerCase()} interview`);
setView({ type: "initial" });
currentSessionIdRef.current = null;
setLockSessionId(null);
@@ -227,8 +228,8 @@ export function MilestoneSliceInterviewModal({
await skipInterview(targetId, projectId);
onApplied();
setView({ type: "applied" });
} catch (err: any) {
setError(err.message || `Failed to skip ${targetLabel.toLowerCase()} interview`);
} catch (err) {
setError(getErrorMessage(err) || `Failed to skip ${targetLabel.toLowerCase()} interview`);
setIsApplying(false);
}
}, [onApplied, projectId, skipInterview, targetId, targetLabel]);
@@ -395,10 +396,10 @@ export function MilestoneSliceInterviewModal({
try {
connectToInterviewStream(sessionId);
await respondToInterview(sessionId, responses, projectId, sessionTabId);
} catch (err: any) {
} catch (err) {
streamConnectionRef.current?.close();
streamConnectionRef.current = null;
setError(err.message || "Failed to submit response");
setError(getErrorMessage(err) || "Failed to submit response");
setView({ type: "question", sessionId, question: view.question });
}
},
@@ -415,8 +416,8 @@ export function MilestoneSliceInterviewModal({
await applyInterview(view.sessionId, editedSummary || undefined, projectId);
onApplied();
setView({ type: "applied" });
} catch (err: any) {
setError(err.message || "Failed to apply interview results");
} catch (err) {
setError(getErrorMessage(err) || "Failed to apply interview results");
setIsApplying(false);
}
}, [applyInterview, editedSummary, onApplied, projectId, view]);

View File

@@ -1,5 +1,6 @@
import { useState, useCallback, useEffect, useMemo, useRef } from "react";
import type { PlanningQuestion } from "@fusion/core";
import { getErrorMessage } from "@fusion/core";
import {
startMissionInterview,
respondToMissionInterview,
@@ -158,8 +159,8 @@ export function MissionInterviewModal({
setLoadedModels(resp.models);
setFavoriteProviders(resp.favoriteProviders);
setFavoriteModels(resp.favoriteModels);
} catch (err: any) {
setModelsError(err.message || "Failed to load models");
} catch (err) {
setModelsError(getErrorMessage(err) || "Failed to load models");
} finally {
setModelsLoading(false);
}
@@ -322,9 +323,9 @@ export function MissionInterviewModal({
connectToMissionInterviewStream(sessionId);
setResponseHistory([]);
} catch (err: any) {
} catch (err) {
setIsReconnecting(false);
setError(err.message || "Failed to start interview session");
setError(getErrorMessage(err) || "Failed to start interview session");
setView({ type: "initial" });
currentSessionIdRef.current = null;
setLockSessionId(null);
@@ -593,10 +594,10 @@ export function MissionInterviewModal({
connectToMissionInterviewStream(sessionId);
await respondToMissionInterview(sessionId, responses, projectId, sessionTabId);
setHasProgress(true);
} catch (err: any) {
} catch (err) {
streamConnectionRef.current?.close();
streamConnectionRef.current = null;
setError(err.message || "Failed to submit response");
setError(getErrorMessage(err) || "Failed to submit response");
setView({ type: "question", sessionId, question: view.question });
}
},
@@ -619,9 +620,9 @@ export function MissionInterviewModal({
currentSessionIdRef.current = retrySessionId;
setLockSessionId(retrySessionId);
await retryMissionInterviewSession(retrySessionId, projectId, sessionTabId);
} catch (err: any) {
let retryError = err;
const retryErrorMessage = err?.message || "";
} catch (err) {
let retryError: unknown = err;
const retryErrorMessage = getErrorMessage(err) || "";
if (retryErrorMessage.includes("not in an error state")) {
try {
@@ -678,7 +679,7 @@ export function MissionInterviewModal({
setIsReconnecting(false);
return;
} catch (sessionRefreshError: any) {
} catch (sessionRefreshError) {
retryError = sessionRefreshError;
}
}
@@ -688,7 +689,7 @@ export function MissionInterviewModal({
setView({
type: "error",
sessionId: retrySessionId,
errorMessage: retryError?.message || "Retry failed. Please try again.",
errorMessage: getErrorMessage(retryError) || "Retry failed. Please try again.",
});
setIsReconnecting(false);
} finally {
@@ -723,8 +724,8 @@ export function MissionInterviewModal({
currentSessionIdRef.current = null;
setLockSessionId(null);
onClose();
} catch (err: any) {
setError(err.message || "Failed to create mission");
} catch (err) {
setError(getErrorMessage(err) || "Failed to create mission");
setIsCreating(false);
}
}, [view, editedSummary, onMissionCreated, onClose, projectId]);
@@ -867,8 +868,8 @@ export function MissionInterviewModal({
setFavoriteProviders(resp.favoriteProviders);
setFavoriteModels(resp.favoriteModels);
setModelsError(null);
} catch (err: any) {
setModelsError(err.message || "Failed to load models");
} catch (err) {
setModelsError(getErrorMessage(err) || "Failed to load models");
} finally {
setModelsLoading(false);
}

View File

@@ -1,4 +1,5 @@
import { useState, useEffect, useCallback, useRef, useMemo } from "react";
import { getErrorMessage } from "@fusion/core";
import {
X,
Plus,
@@ -659,8 +660,8 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
const data = await fetchMissions(projectId);
setMissions(data);
void loadMissionHealth(data);
} catch (err: any) {
addToast(err.message || "Failed to load missions", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to load missions", "error");
} finally {
setLoading(false);
}
@@ -699,8 +700,8 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
setSelectedMilestoneId(null);
setValidationTelemetry(null);
}
} catch (err: any) {
addToast(err.message || "Failed to load mission details", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to load mission details", "error");
} finally {
setDetailLoading(false);
}
@@ -816,8 +817,8 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
scrollActivityToLatest("auto");
});
}
} catch (err: any) {
addToast(err.message || "Failed to load mission activity", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to load mission activity", "error");
} finally {
if (!append) {
setEventsLoading(false);
@@ -1170,8 +1171,8 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
}
await loadMissions();
handleCancelMission();
} catch (err: any) {
addToast(err.message || "Failed to save mission", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to save mission", "error");
} finally {
setSaving(false);
}
@@ -1186,8 +1187,8 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
}
await loadMissions();
setDeleteConfirmId(null);
} catch (err: any) {
addToast(err.message || "Failed to delete mission", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to delete mission", "error");
}
}, [addToast, loadMissions, selectedMission, projectId]);
@@ -1241,8 +1242,8 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
}
await loadMissionDetail(selectedMission!.id);
handleCancelMilestone();
} catch (err: any) {
addToast(err.message || "Failed to save milestone", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to save milestone", "error");
} finally {
setSaving(false);
}
@@ -1254,8 +1255,8 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
addToast("Milestone deleted", "success");
await loadMissionDetail(selectedMission!.id);
setDeleteConfirmId(null);
} catch (err: any) {
addToast(err.message || "Failed to delete milestone", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to delete milestone", "error");
}
}, [addToast, loadMissionDetail, selectedMission, projectId]);
@@ -1338,8 +1339,8 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
}
await loadMissionDetail(selectedMission!.id);
handleCancelSlice();
} catch (err: any) {
addToast(err.message || "Failed to save slice", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to save slice", "error");
} finally {
setSaving(false);
}
@@ -1351,8 +1352,8 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
addToast("Slice deleted", "success");
await loadMissionDetail(selectedMission!.id);
setDeleteConfirmId(null);
} catch (err: any) {
addToast(err.message || "Failed to delete slice", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to delete slice", "error");
}
}, [addToast, loadMissionDetail, selectedMission, projectId]);
@@ -1361,8 +1362,8 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
await activateSlice(sliceId, projectId);
addToast("Slice activated", "success");
await loadMissionDetail(selectedMission!.id);
} catch (err: any) {
addToast(err.message || "Failed to activate slice", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to activate slice", "error");
}
}, [addToast, loadMissionDetail, selectedMission, projectId]);
@@ -1430,8 +1431,8 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
}
await loadMissionDetail(selectedMission!.id);
handleCancelFeature();
} catch (err: any) {
addToast(err.message || "Failed to save feature", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to save feature", "error");
} finally {
setSaving(false);
}
@@ -1443,8 +1444,8 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
addToast("Feature deleted", "success");
await loadMissionDetail(selectedMission!.id);
setDeleteConfirmId(null);
} catch (err: any) {
addToast(err.message || "Failed to delete feature", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to delete feature", "error");
}
}, [addToast, loadMissionDetail, selectedMission, projectId]);
@@ -1460,8 +1461,8 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
await loadMissionDetail(selectedMission!.id);
setLinkTaskFeatureId(null);
setSelectedTaskId("");
} catch (err: any) {
addToast(err.message || "Failed to link feature to task", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to link feature to task", "error");
}
}, [linkTaskFeatureId, selectedTaskId, addToast, loadMissionDetail, selectedMission, projectId]);
@@ -1470,8 +1471,8 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
await unlinkFeatureFromTask(featureId, projectId);
addToast("Feature unlinked from task", "success");
await loadMissionDetail(selectedMission!.id);
} catch (err: any) {
addToast(err.message || "Failed to unlink feature", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to unlink feature", "error");
}
}, [addToast, loadMissionDetail, selectedMission, projectId]);
@@ -1482,8 +1483,8 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
await triageFeature(featureId, undefined, undefined, projectId);
addToast("Feature triaged — task created", "success");
await loadMissionDetail(selectedMission!.id);
} catch (err: any) {
addToast(err.message || "Failed to triage feature", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to triage feature", "error");
} finally {
setSaving(false);
}
@@ -1522,8 +1523,8 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
const result = await triageAllSliceFeatures(sliceId, projectId);
addToast(`Triaged ${result.count} feature${result.count !== 1 ? "s" : ""}`, "success");
await loadMissionDetail(selectedMission!.id);
} catch (err: any) {
addToast(err.message || "Failed to triage slice features", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to triage slice features", "error");
} finally {
setSaving(false);
}
@@ -1574,8 +1575,8 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
await loadValidationRollup(milestoneId);
setIsCreatingAssertion(false);
setAssertionForm({ title: "", assertion: "", status: "pending" });
} catch (err: any) {
addToast(err.message || "Failed to create assertion", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to create assertion", "error");
} finally {
setSaving(false);
}
@@ -1612,8 +1613,8 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
await loadAssertionsForMilestone(milestoneId);
await loadValidationRollup(milestoneId);
handleCancelAssertion();
} catch (err: any) {
addToast(err.message || "Failed to update assertion", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to update assertion", "error");
} finally {
setSaving(false);
}
@@ -1658,8 +1659,8 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
addToast("Feature linked to assertion", "success");
await loadLinkedFeaturesForAssertion(assertionId);
setFeaturePickerOpenForAssertion(null);
} catch (err: any) {
addToast(err.message || "Failed to link feature", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to link feature", "error");
} finally {
setLinkingAssertions((prev) => {
const next = new Set(prev);
@@ -1676,8 +1677,8 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
await unlinkFeatureFromAssertion(featureId, assertionId, projectId);
addToast("Feature unlinked from assertion", "success");
await loadLinkedFeaturesForAssertion(assertionId);
} catch (err: any) {
addToast(err.message || "Failed to unlink feature", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to unlink feature", "error");
} finally {
setUnlinkingFeatures((prev) => {
const next = new Set(prev);
@@ -1701,8 +1702,8 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
next.set(featureId, snapshot);
return next;
});
} catch (err: any) {
addToast(err.message || "Failed to trigger validation", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to trigger validation", "error");
} finally {
setValidatingFeatures((prev) => {
const next = new Set(prev);
@@ -1822,8 +1823,8 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
addToast("Mission resumed", "success");
await loadMissionDetail(missionId);
loadMissions();
} catch (err: any) {
addToast(err.message || "Failed to resume mission", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to resume mission", "error");
}
}, [addToast, loadMissionDetail, loadMissions, projectId]);
@@ -1835,8 +1836,8 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
addToast(`Mission stopped (${count} task${count !== 1 ? "s" : ""} paused)`, "success");
await loadMissionDetail(missionId);
loadMissions();
} catch (err: any) {
addToast(err.message || "Failed to stop mission", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to stop mission", "error");
}
}, [addToast, loadMissionDetail, loadMissions, projectId]);
@@ -1847,8 +1848,8 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
addToast("Mission started — first slice activated", "success");
await loadMissionDetail(missionId);
loadMissions();
} catch (err: any) {
addToast(err.message || "Failed to start mission", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to start mission", "error");
}
}, [addToast, loadMissionDetail, loadMissions, projectId]);
@@ -1861,8 +1862,8 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
// Reload mission detail to reflect updated fields
await loadMissionDetail(missionId);
loadMissions();
} catch (err: any) {
addToast(err.message || "Failed to update autopilot", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to update autopilot", "error");
}
}, [addToast, loadMissionDetail, loadMissions, projectId]);
@@ -3454,9 +3455,8 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
)}
{/* Mission items */}
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
{missions.map((mission: any) => {
const m = mission as { id: string; title: string; description?: string; status: string; summary?: { totalMilestones: number; completedMilestones: number; totalFeatures: number; completedFeatures: number; progressPercent: number } };
{missions.map((mission) => {
const m = mission;
const selId = selectedMission as { id: string } | null;
const isSelected = selId && selId.id === m.id;
const statusColors = missionStatusColors[m.status as MissionStatus] || { bg: "", text: "" };

View File

@@ -2,6 +2,7 @@ import { useState, useEffect, useCallback, useMemo, useRef } from "react";
import { fetchModels, updateTask, updateGlobalSettings } from "../api";
import type { ModelInfo } from "../api";
import type { Settings, Task, TaskDetail } from "@fusion/core";
import { getErrorMessage } from "@fusion/core";
import type { ToastType } from "../hooks/useToast";
import { CustomModelDropdown } from "./CustomModelDropdown";
import { ProviderIcon } from "./ProviderIcon";
@@ -289,7 +290,7 @@ export function ModelSelectorTab({ task, addToast, onTaskUpdated, settings }: Mo
getSuccessToastMessage(target, targetSelections[target]),
"success",
);
} catch (err: any) {
} catch (err) {
if (activeTaskIdRef.current !== requestTaskId) {
return;
}
@@ -302,7 +303,7 @@ export function ModelSelectorTab({ task, addToast, onTaskUpdated, settings }: Mo
setSelectedPlanning(previousSavedPlanning);
}
addToast(err.message || "Failed to save model settings", "error");
addToast(getErrorMessage(err) || "Failed to save model settings", "error");
} finally {
if (activeTaskIdRef.current === requestTaskId) {
setSavingTarget(null);
@@ -390,13 +391,13 @@ export function ModelSelectorTab({ task, addToast, onTaskUpdated, settings }: Mo
"success",
);
}
} catch (err: any) {
} catch (err) {
if (activeTaskIdRef.current !== requestTaskId) {
return;
}
setSelectedThinking(previousThinking);
addToast(err.message || "Failed to save thinking level", "error");
addToast(getErrorMessage(err) || "Failed to save thinking level", "error");
} finally {
if (activeTaskIdRef.current === requestTaskId) {
setSavingTarget(null);

View File

@@ -1,5 +1,6 @@
import { useState, useCallback, useEffect, useRef } from "react";
import type { Task, TaskCreateInput } from "@fusion/core";
import { getErrorMessage } from "@fusion/core";
import type { ToastType } from "../hooks/useToast";
import { uploadAttachment, fetchAgents } from "../api";
import type { Agent } from "../api";
@@ -73,8 +74,9 @@ export function NewTaskModal({ isOpen, onClose, projectId, tasks, onCreateTask,
const result = await fetchAgents(undefined, projectId);
setAgents(result);
setShowAgentPicker(true);
} catch (err: any) {
addToast(err?.message ? `Failed to load agents: ${err.message}` : "Failed to load agents", "error");
} catch (err) {
const msg = getErrorMessage(err);
addToast(msg ? `Failed to load agents: ${msg}` : "Failed to load agents", "error");
setShowAgentPicker(false);
} finally {
setAgentsLoading(false);
@@ -237,8 +239,8 @@ export function NewTaskModal({ isOpen, onClose, projectId, tasks, onCreateTask,
addToast(`Created ${task.id}`, "success");
onClose();
} catch (err: any) {
addToast(err.message || "Failed to create task", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to create task", "error");
} finally {
setIsSubmitting(false);
}

View File

@@ -1,5 +1,6 @@
import { useState, useCallback, useEffect, useRef, useMemo } from "react";
import type { Task, PlanningQuestion, PlanningSummary } from "@fusion/core";
import { getErrorMessage } from "@fusion/core";
import {
startPlanningStreaming,
respondToPlanning,
@@ -145,8 +146,8 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
setLoadedModels(response.models);
setFavoriteProviders(response.favoriteProviders);
setFavoriteModels(response.favoriteModels);
} catch (err: any) {
setModelsError(err?.message || "Failed to load models");
} catch (err) {
setModelsError(getErrorMessage(err) || "Failed to load models");
} finally {
setModelsLoading(false);
}
@@ -311,9 +312,9 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
connectToPlanningStream(sessionId);
setResponseHistory([]);
} catch (err: any) {
} catch (err) {
setIsReconnecting(false);
setError(err.message || "Failed to start planning session");
setError(getErrorMessage(err) || "Failed to start planning session");
setView({ type: "initial" });
currentSessionIdRef.current = null;
setLockSessionId(null);
@@ -584,8 +585,8 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
// Submit response - AI will broadcast events via the already-connected stream
await respondToPlanning(sessionId, responses, projectId, sessionTabId);
// Events (question/summary) will arrive via the existing SSE stream
} catch (err: any) {
setError(err.message || "Failed to submit response");
} catch (err) {
setError(getErrorMessage(err) || "Failed to submit response");
setView({ type: "question", session });
}
},
@@ -609,9 +610,9 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
currentSessionIdRef.current = retryTarget.sessionId;
setLockSessionId(retryTarget.sessionId);
await retryPlanningSession(retryTarget.sessionId, projectId, sessionTabId);
} catch (err: any) {
let retryError = err;
const retryErrorMessage = err?.message || "";
} catch (err) {
let retryError: unknown = err;
const retryErrorMessage = getErrorMessage(err) || "";
if (retryErrorMessage.includes("not in an error state")) {
try {
@@ -661,7 +662,7 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
setIsReconnecting(false);
return;
} catch (sessionRefreshError: any) {
} catch (sessionRefreshError) {
retryError = sessionRefreshError;
}
}
@@ -671,7 +672,7 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
setView({
type: "error",
session: retryTarget,
errorMessage: retryError?.message || "Retry failed. Please try again.",
errorMessage: getErrorMessage(retryError) || "Retry failed. Please try again.",
});
setIsReconnecting(false);
} finally {
@@ -689,8 +690,8 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
const task = await createTaskFromPlanning(view.session.sessionId, editedSummary ?? undefined, projectId);
onTaskCreated(task);
handleCancel();
} catch (err: any) {
setError(err.message || "Failed to create task");
} catch (err) {
setError(getErrorMessage(err) || "Failed to create task");
setView({ type: "summary", session: view.session, summary: view.summary });
}
}, [editedSummary, view, projectId, onTaskCreated, handleCancel]);
@@ -710,8 +711,8 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
subtasks: result.subtasks,
dirty: false,
});
} catch (err: any) {
setError(err.message || "Failed to start breakdown");
} catch (err) {
setError(getErrorMessage(err) || "Failed to start breakdown");
setView({ type: "summary", session: view.session, summary: view.summary });
}
}, [editedSummary, view, projectId]);
@@ -738,8 +739,8 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
currentSessionIdRef.current = null;
setLockSessionId(null);
onClose();
} catch (err: any) {
setError(err.message || "Failed to create tasks");
} catch (err) {
setError(getErrorMessage(err) || "Failed to create tasks");
setView({ type: "breakdown", sessionId: view.sessionId, subtasks: view.subtasks, dirty: view.dirty });
}
}, [view, onTasksCreated, onClose, projectId]);

View File

@@ -1,6 +1,7 @@
import { useState, useCallback } from "react";
import { GitPullRequest, ExternalLink, RefreshCw, Plus, MessageSquare, CircleDot, XCircle, GitMerge } from "lucide-react";
import type { PrInfo } from "@fusion/core";
import { getErrorMessage } from "@fusion/core";
import { createPr, refreshPrStatus, type PrRefreshResponse } from "../api";
import type { ToastType } from "../hooks/useToast";
@@ -54,8 +55,8 @@ export function PrSection({
setPrTitle("");
setPrBody("");
addToast(`Created PR #${newPr.number}`, "success");
} catch (err: any) {
addToast(err.message || "Failed to create PR", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to create PR", "error");
} finally {
setIsCreating(false);
}
@@ -70,8 +71,8 @@ export function PrSection({
setRefreshState(updated);
onPrUpdated(updated.prInfo);
addToast("PR status refreshed", "success");
} catch (err: any) {
addToast(err.message || "Failed to refresh PR", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to refresh PR", "error");
} finally {
setIsRefreshing(false);
}

View File

@@ -2,6 +2,7 @@ import { useState, useCallback, useRef, useEffect } from "react";
import { createPortal } from "react-dom";
import type { ToastType } from "../hooks/useToast";
import type { Task, TaskCreateInput, Settings } from "@fusion/core";
import { getErrorMessage } from "@fusion/core";
import type { ModelInfo, RefinementType, Agent } from "../api";
import { fetchModels, fetchSettings, refineText, getRefineErrorMessage, updateGlobalSettings, fetchAgents, uploadAttachment } from "../api";
import { Link, Paperclip, Brain, Lightbulb, ListTree, Sparkles, Save, ChevronDown, ChevronUp, ChevronRight, Bot } from "lucide-react";
@@ -461,8 +462,8 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
// Clear input for rapid entry
resetForm();
// Note: Focus restoration is handled by useEffect when isSubmitting becomes false
} catch (err: any) {
addToast(err.message || "Failed to create task", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to create task", "error");
// Keep input content on failure so user can retry
} finally {
setIsSubmitting(false);
@@ -1019,7 +1020,7 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
textareaRef.current.style.height = "auto";
textareaRef.current.style.height = `${Math.min(textareaRef.current.scrollHeight, 200)}px`;
}
} catch (err: any) {
} catch (err) {
const errorMessage = getRefineErrorMessage(err);
addToast(errorMessage, "error");
} finally {
@@ -1050,8 +1051,8 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
if (!parentFavoriteModels) {
setFavoriteModels(response.favoriteModels);
}
} catch (err: any) {
setModelsError(err?.message || "Failed to load models");
} catch (err) {
setModelsError(getErrorMessage(err) || "Failed to load models");
} finally {
setModelsLoading(false);
}
@@ -1071,8 +1072,9 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
setAgentsProjectId(projectId);
setShowAgentPicker(true);
updateAgentPickerPosition();
} catch (err: any) {
addToast(err?.message ? `Failed to load agents: ${err.message}` : "Failed to load agents", "error");
} catch (err) {
const msg = getErrorMessage(err);
addToast(msg ? `Failed to load agents: ${msg}` : "Failed to load agents", "error");
setShowAgentPicker(false);
} finally {
setAgentsLoading(false);

View File

@@ -155,7 +155,7 @@ export function RoutineCard({ routine, onEdit, onDelete, onRun, onToggle, runnin
// Get cron expression if available (from trigger or direct field)
const cronExpression = routine.trigger.type === "cron"
? (routine.trigger as any).cronExpression || routine.cronExpression || ""
? (("cronExpression" in routine.trigger ? routine.trigger.cronExpression : undefined) as string | undefined) || routine.cronExpression || ""
: routine.cronExpression || "";
return (

View File

@@ -1,6 +1,7 @@
import { useState, useEffect, useCallback, useMemo } from "react";
import { Plus, Zap, Globe, Folder, X } from "lucide-react";
import type { Routine, RoutineCreateInput } from "@fusion/core";
import { getErrorMessage } from "@fusion/core";
import {
fetchRoutines,
createRoutine,
@@ -46,8 +47,8 @@ export function ScheduledTasksModal({ onClose, addToast, projectId }: ScheduledT
try {
const data = await fetchRoutines(scopeOptions);
setRoutines(data);
} catch (err: any) {
addToast(err.message || "Failed to load routines", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to load routines", "error");
}
}, [addToast, scopeOptions]);
@@ -95,8 +96,8 @@ export function ScheduledTasksModal({ onClose, addToast, projectId }: ScheduledT
addToast("Routine created", "success");
setRoutineView("list");
await loadRoutines();
} catch (err: any) {
addToast(err.message || "Failed to create routine", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to create routine", "error");
}
},
[addToast, loadRoutines, scopeOptions],
@@ -116,8 +117,8 @@ export function ScheduledTasksModal({ onClose, addToast, projectId }: ScheduledT
setRoutineView("list");
setEditingRoutine(undefined);
await loadRoutines();
} catch (err: any) {
addToast(err.message || "Failed to update routine", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to update routine", "error");
}
},
[editingRoutine, addToast, loadRoutines, scopeOptions],
@@ -129,8 +130,8 @@ export function ScheduledTasksModal({ onClose, addToast, projectId }: ScheduledT
await deleteRoutine(routine.id, scopeOptions);
addToast(`Deleted "${routine.name}"`, "success");
await loadRoutines();
} catch (err: any) {
addToast(err.message || "Failed to delete routine", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to delete routine", "error");
}
},
[addToast, loadRoutines, scopeOptions],
@@ -147,8 +148,8 @@ export function ScheduledTasksModal({ onClose, addToast, projectId }: ScheduledT
addToast(`"${routine.name}" failed: ${result.error || "Unknown error"}`, "error");
}
await loadRoutines();
} catch (err: any) {
addToast(err.message || "Failed to run routine", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to run routine", "error");
} finally {
setRunningRoutineId(null);
}
@@ -165,8 +166,8 @@ export function ScheduledTasksModal({ onClose, addToast, projectId }: ScheduledT
"success",
);
await loadRoutines();
} catch (err: any) {
addToast(err.message || "Failed to toggle routine", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to toggle routine", "error");
}
},
[addToast, loadRoutines, scopeOptions],

View File

@@ -1,4 +1,5 @@
import { useState, useEffect, useCallback } from "react";
import { getErrorMessage } from "@fusion/core";
import { fetchScripts, addScript, removeScript, type ScriptEntry } from "../api";
import type { ToastType } from "../hooks/useToast";
import {
@@ -55,8 +56,8 @@ export function ScriptsModal({ isOpen, onClose, addToast, projectId, onRunScript
setLoading(true);
const data = await fetchScripts(projectId);
setScripts(data);
} catch (err: any) {
addToast(err.message || "Failed to load scripts", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to load scripts", "error");
} finally {
setLoading(false);
}
@@ -126,11 +127,12 @@ export function ScriptsModal({ isOpen, onClose, addToast, projectId, onRunScript
setForm(EMPTY_FORM);
setNameError(null);
await loadScripts();
} catch (err: any) {
if (err.message?.includes("already exists")) {
} catch (err) {
const msg = getErrorMessage(err);
if (msg?.includes("already exists")) {
addToast("A script with this name already exists", "error");
} else {
addToast(err.message || "Failed to save script", "error");
addToast(msg || "Failed to save script", "error");
}
} finally {
setSaving(false);
@@ -147,8 +149,8 @@ export function ScriptsModal({ isOpen, onClose, addToast, projectId, onRunScript
setForm(EMPTY_FORM);
}
await loadScripts();
} catch (err: any) {
addToast(err.message || "Failed to delete script", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to delete script", "error");
}
}, [isEditing, addToast, loadScripts, projectId]);

View File

@@ -1,7 +1,7 @@
import { useState, useEffect, useCallback, useRef } from "react";
import { Globe, Folder } from "lucide-react";
import { THINKING_LEVELS, isGlobalSettingsKey, isProjectSettingsKey } from "@fusion/core";
import type { Settings, GlobalSettings, ThemeMode, ColorTheme, ModelPreset, NtfyNotificationEvent, AgentPromptsConfig } from "@fusion/core";
import { THINKING_LEVELS, isGlobalSettingsKey, isProjectSettingsKey, getErrorMessage } from "@fusion/core";
import type { Settings, GlobalSettings, ThemeMode, ColorTheme, ModelPreset, NtfyNotificationEvent, AgentPromptsConfig, ThinkingLevel } from "@fusion/core";
import { fetchSettings, fetchSettingsByScope, updateSettings, updateGlobalSettings, fetchAuthStatus, loginProvider, logoutProvider, saveApiKey, clearApiKey, fetchModels, testNtfyNotification, fetchBackups, createBackup, exportSettings, importSettings, fetchMemoryFile, fetchMemoryFiles, saveMemoryFile, compactMemory, fetchGlobalConcurrency, updateGlobalConcurrency, installQmd, testMemoryRetrieval, fetchGitRemotesDetailed } from "../api";
import type { AuthProvider, ModelInfo, BackupListResponse, SettingsExportData, MemoryFileInfo, MemoryRetrievalTestResult, GitRemoteDetailed } from "../api";
import { useMemoryBackendStatus } from "../hooks/useMemoryBackendStatus";
@@ -95,6 +95,9 @@ const KNOWN_EXPERIMENTAL_FEATURES: Record<string, string> = {
export type SectionId = SettingsSection["id"];
/** Local form state extends Settings with a worktreeInitCommand override and lets tokenCap carry null (delete semantic). */
type SettingsFormState = Settings & { worktreeInitCommand?: string; tokenCap?: number | null };
interface SettingsModalProps {
onClose: () => void;
addToast: (message: string, type?: ToastType) => void;
@@ -124,7 +127,7 @@ export function SettingsModal({
onColorThemeChange,
onReopenOnboarding,
}: SettingsModalProps) {
const [form, setForm] = useState<Settings & { worktreeInitCommand?: string }>({
const [form, setForm] = useState<SettingsFormState>({
maxConcurrent: 2,
maxTriageConcurrent: 2,
maxWorktrees: 4,
@@ -256,7 +259,7 @@ export function SettingsModal({
setLoading(false);
})
.catch((err) => {
addToast(err.message, "error");
addToast(getErrorMessage(err), "error");
setLoading(false);
});
}, [addToast, projectId]);
@@ -343,9 +346,9 @@ export function SettingsModal({
setMemoryContent(content);
setMemoryDirty(false);
})
.catch((err: any) => {
.catch((err) => {
if (cancelled) return;
addToast(err?.message || "Failed to load project memory", "error");
addToast(getErrorMessage(err) || "Failed to load project memory", "error");
setMemoryContent("");
})
.finally(() => {
@@ -397,8 +400,8 @@ export function SettingsModal({
// Continue polling on transient errors
}
}, 2000);
} catch (err: any) {
addToast(err.message || "Login failed", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Login failed", "error");
setAuthActionInProgress(null);
}
}, [addToast]);
@@ -409,8 +412,8 @@ export function SettingsModal({
await logoutProvider(providerId);
await loadAuthStatus();
addToast("Logged out", "success");
} catch (err: any) {
addToast(err.message || "Logout failed", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Logout failed", "error");
} finally {
setAuthActionInProgress(null);
}
@@ -437,8 +440,8 @@ export function SettingsModal({
});
await loadAuthStatus();
addToast("API key saved", "success");
} catch (err: any) {
setApiKeyErrors((prev) => ({ ...prev, [providerId]: err.message || "Failed to save API key" }));
} catch (err) {
setApiKeyErrors((prev) => ({ ...prev, [providerId]: getErrorMessage(err) || "Failed to save API key" }));
} finally {
setAuthActionInProgress(null);
}
@@ -460,8 +463,8 @@ export function SettingsModal({
});
await loadAuthStatus();
addToast("API key cleared", "success");
} catch (err: any) {
addToast(err.message || "Failed to clear API key", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to clear API key", "error");
} finally {
setAuthActionInProgress(null);
}
@@ -484,8 +487,8 @@ export function SettingsModal({
} else {
addToast("Failed to send test notification", "error");
}
} catch (err: any) {
addToast(err.message || "Failed to send test notification", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to send test notification", "error");
} finally {
setTestNotificationLoading(false);
}
@@ -503,8 +506,8 @@ export function SettingsModal({
} else {
addToast(result.error || "Failed to create backup", "error");
}
} catch (err: any) {
addToast(err.message || "Failed to create backup", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to create backup", "error");
} finally {
setBackupLoading(false);
}
@@ -532,8 +535,8 @@ export function SettingsModal({
const scopeLabel = scope === "global" ? "global" : scope === "project" ? "project" : "all";
addToast(`Settings exported (${scopeLabel} scope)`, "success");
} catch (err: any) {
addToast(err.message || "Failed to export settings", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to export settings", "error");
}
}, [addToast, activeSectionScope, projectId]);
@@ -549,8 +552,8 @@ export function SettingsModal({
const data = JSON.parse(text) as SettingsExportData;
setImportPreview(data);
setImportDialogOpen(true);
} catch (err: any) {
addToast(`Invalid JSON file: ${err.message}`, "error");
} catch (err) {
addToast(`Invalid JSON file: ${getErrorMessage(err)}`, "error");
setImportFile(null);
} finally {
setImportLoading(false);
@@ -577,8 +580,8 @@ export function SettingsModal({
} else {
addToast(result.error || "Import failed", "error");
}
} catch (err: any) {
addToast(err.message || "Failed to import settings", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to import settings", "error");
} finally {
setImportLoading(false);
}
@@ -803,9 +806,9 @@ export function SettingsModal({
// if current value is undefined AND initial was defined, use null
const initialValue = initialValues?.[key as keyof GlobalSettings];
if (value === undefined && initialValue !== undefined) {
(globalPatch as any)[key] = null; // null means "explicitly clear"
(globalPatch as Record<string, unknown>)[key] = null; // null means "explicitly clear"
} else {
(globalPatch as any)[key] = value;
(globalPatch as Record<string, unknown>)[key] = value;
}
}
}
@@ -837,14 +840,14 @@ export function SettingsModal({
if (value !== initialProjectValue) {
// Detect explicit reset: current is undefined/null but initial was set
if ((value === undefined || value === null) && initialProjectValue !== undefined && initialProjectValue !== null) {
(projectPatch as any)[key] = null; // null-as-delete
(projectPatch as Record<string, unknown>)[key] = null; // null-as-delete
} else if (value !== undefined) {
(projectPatch as any)[key] = value;
(projectPatch as Record<string, unknown>)[key] = value;
}
}
} else {
// For non-model settings: existing behavior
(projectPatch as any)[key] = value;
(projectPatch as Record<string, unknown>)[key] = value;
}
}
@@ -862,8 +865,8 @@ export function SettingsModal({
addToast("Settings saved", "success");
onClose();
} catch (err: any) {
addToast(err.message, "error");
} catch (err) {
addToast(getErrorMessage(err), "error");
}
}, [form, globalMaxConcurrent, prefixError, presetDraft, initialValues, initialScopedValues, onClose, addToast, projectId]);
@@ -872,8 +875,8 @@ export function SettingsModal({
await saveMemoryFile(selectedMemoryPath, memoryContent, projectId);
setMemoryDirty(false);
addToast("Memory saved", "success");
} catch (err: any) {
addToast(err?.message || "Failed to save memory", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to save memory", "error");
}
}, [selectedMemoryPath, memoryContent, projectId, addToast]);
@@ -893,8 +896,8 @@ export function SettingsModal({
setMemoryFiles(files);
addToast("Memory file compacted", "success");
} catch (err: any) {
addToast(err?.message || "Failed to compact memory", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to compact memory", "error");
} finally {
setMemoryCompactLoading(false);
}
@@ -910,8 +913,8 @@ export function SettingsModal({
result.qmdAvailable ? "Memory retrieval test complete" : "qmd is not installed; local fallback was used",
result.qmdAvailable ? "success" : "warning",
);
} catch (err: any) {
addToast(err?.message || "Failed to test memory retrieval", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to test memory retrieval", "error");
} finally {
setMemoryTestLoading(false);
}
@@ -926,8 +929,8 @@ export function SettingsModal({
result.qmdAvailable ? "qmd installed successfully" : "qmd install finished, but qmd is still unavailable",
result.qmdAvailable ? "success" : "warning",
);
} catch (err: any) {
addToast(err?.message || "Failed to install qmd", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to install qmd", "error");
} finally {
setQmdInstallLoading(false);
}
@@ -1143,7 +1146,7 @@ export function SettingsModal({
value={form.defaultThinkingLevel || ""}
onChange={(e) => {
const val = e.target.value;
setForm((f) => ({ ...f, defaultThinkingLevel: val || undefined } as any));
setForm((f) => ({ ...f, defaultThinkingLevel: (val as ThinkingLevel) || undefined }));
}}
>
<option value="">Default</option>
@@ -1254,18 +1257,18 @@ export function SettingsModal({
id="tokenCap"
type="number"
placeholder="No cap"
value={(form as any).tokenCap ?? ""}
value={form.tokenCap ?? ""}
onChange={(e) => {
const val = e.target.value;
setForm((f) => ({ ...f, tokenCap: val ? parseInt(val, 10) : null } as any));
setForm((f) => ({ ...f, tokenCap: val ? parseInt(val, 10) : null } as SettingsFormState));
}}
/>
{(form as any).tokenCap != null && (
{form.tokenCap != null && (
<button
type="button"
className="btn btn-ghost btn-sm"
title="Reset to default (no cap)"
onClick={() => setForm((f) => ({ ...f, tokenCap: null } as any))}
onClick={() => setForm((f) => ({ ...f, tokenCap: null } as unknown as SettingsFormState))}
style={{ whiteSpace: "nowrap" }}
>
Reset
@@ -1758,7 +1761,7 @@ export function SettingsModal({
value={form.maxConcurrent ?? ""}
onChange={(e) => {
const val = e.target.value;
setForm((f) => ({ ...f, maxConcurrent: val === "" ? undefined : Number(val) } as any));
setForm((f) => ({ ...f, maxConcurrent: val === "" ? undefined : Number(val) } as SettingsFormState));
}}
/>
</div>
@@ -1772,7 +1775,7 @@ export function SettingsModal({
value={form.maxTriageConcurrent ?? ""}
onChange={(e) => {
const val = e.target.value;
setForm((f) => ({ ...f, maxTriageConcurrent: val === "" ? undefined : Number(val) } as any));
setForm((f) => ({ ...f, maxTriageConcurrent: val === "" ? undefined : Number(val) } as SettingsFormState));
}}
/>
<small>Maximum concurrent triage/specification agents</small>
@@ -1787,7 +1790,7 @@ export function SettingsModal({
value={form.pollIntervalMs ?? ""}
onChange={(e) => {
const val = e.target.value;
setForm((f) => ({ ...f, pollIntervalMs: val === "" ? undefined : Number(val) } as any));
setForm((f) => ({ ...f, pollIntervalMs: val === "" ? undefined : Number(val) } as SettingsFormState));
}}
/>
</div>
@@ -1975,7 +1978,7 @@ export function SettingsModal({
value={form.maxWorktrees ?? ""}
onChange={(e) => {
const val = e.target.value;
setForm((f) => ({ ...f, maxWorktrees: val === "" ? undefined : Number(val) } as any));
setForm((f) => ({ ...f, maxWorktrees: val === "" ? undefined : Number(val) } as SettingsFormState));
}}
/>
<small>Limits total git worktrees including in-review tasks</small>

View File

@@ -1,5 +1,6 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import type { Task } from "@fusion/core";
import { getErrorMessage } from "@fusion/core";
import {
startSubtaskBreakdown,
retrySubtaskSession,
@@ -242,8 +243,8 @@ export function SubtaskBreakdownModal({ isOpen, onClose, initialDescription, onT
const { sessionId } = await startSubtaskBreakdown(localDescription.trim(), projectId);
setView({ type: "generating", sessionId });
connectToSubtaskStream(sessionId);
} catch (err: any) {
setError(err.message || "Failed to start subtask breakdown");
} catch (err) {
setError(getErrorMessage(err) || "Failed to start subtask breakdown");
setView({ type: "initial" });
}
}, [connectToSubtaskStream, localDescription, projectId]);
@@ -295,8 +296,8 @@ export function SubtaskBreakdownModal({ isOpen, onClose, initialDescription, onT
errorMessage: session.error ?? "Session encountered an error",
});
}
} catch (err: any) {
setError(err.message || "Failed to resume session");
} catch (err) {
setError(getErrorMessage(err) || "Failed to resume session");
}
})();
}, [connectToSubtaskStream, isOpen, resumeSessionId, view.type, projectId]);
@@ -478,8 +479,8 @@ export function SubtaskBreakdownModal({ isOpen, onClose, initialDescription, onT
onTasksCreated(result.tasks);
resetState();
onClose();
} catch (err: any) {
setError(err.message || "Failed to create tasks");
} catch (err) {
setError(getErrorMessage(err) || "Failed to create tasks");
setView({ type: "editing", sessionId });
}
}, [isInvalid, onClose, onTasksCreated, parentTaskId, projectId, resetState, sessionId, subtasks]);
@@ -498,9 +499,9 @@ export function SubtaskBreakdownModal({ isOpen, onClose, initialDescription, onT
try {
await retrySubtaskSession(retrySessionId, projectId, sessionTabId);
} catch (err: any) {
let retryError = err;
const retryErrorMessage = err?.message || "";
} catch (err) {
let retryError: unknown = err;
const retryErrorMessage = getErrorMessage(err) || "";
if (retryErrorMessage.includes("not in an error state")) {
try {
@@ -536,7 +537,7 @@ export function SubtaskBreakdownModal({ isOpen, onClose, initialDescription, onT
setIsReconnecting(false);
return;
} catch (sessionRefreshError: any) {
} catch (sessionRefreshError) {
retryError = sessionRefreshError;
}
}
@@ -546,7 +547,7 @@ export function SubtaskBreakdownModal({ isOpen, onClose, initialDescription, onT
setView({
type: "error",
sessionId: retrySessionId,
errorMessage: retryError?.message || "Retry failed. Please try again.",
errorMessage: getErrorMessage(retryError) || "Retry failed. Please try again.",
});
setIsReconnecting(false);
} finally {

View File

@@ -1,7 +1,7 @@
import { memo, useCallback, useState, useRef, useEffect, useMemo } from "react";
import { Link, Clock, Layers, Pencil, ChevronDown, Folder, Target, Bot, Trash2 } from "lucide-react";
import type { Task, TaskDetail, Column, PrInfo, IssueInfo } from "@fusion/core";
import { COLUMN_LABELS, VALID_TRANSITIONS } from "@fusion/core";
import { COLUMN_LABELS, VALID_TRANSITIONS, getErrorMessage } from "@fusion/core";
import { fetchTaskDetail, uploadAttachment, fetchMission, fetchAgent } from "../api";
import { GitHubBadge } from "./GitHubBadge";
import { pickPreferredBadge } from "./TaskCardBadge";
@@ -438,8 +438,8 @@ function TaskCardComponent({
try {
await uploadAttachment(task.id, file, projectId);
addToast(`Attached ${file.name} to ${task.id}`, "success");
} catch (err: any) {
addToast(`Failed to attach ${file.name}: ${err.message}`, "error");
} catch (err) {
addToast(`Failed to attach ${file.name}: ${getErrorMessage(err)}`, "error");
}
}
}, [task.id, isFileDrag, addToast]);
@@ -640,8 +640,8 @@ function TaskCardComponent({
});
addToast(`Updated ${task.id}`, "success");
setIsEditing(false);
} catch (err: any) {
addToast(`Failed to update ${task.id}: ${err.message}`, "error");
} catch (err) {
addToast(`Failed to update ${task.id}: ${getErrorMessage(err)}`, "error");
// Stay in edit mode on error so user can retry
} finally {
setIsSaving(false);

View File

@@ -1,6 +1,7 @@
import { useState, useEffect, useCallback } from "react";
import { FileCode, ChevronDown, ChevronRight, ChevronLeft, AlertCircle, GitCommit, WrapText, Maximize2 } from "lucide-react";
import type { MergeDetails, Column } from "@fusion/core";
import { getErrorMessage } from "@fusion/core";
import { fetchTaskDiff, type TaskDiff } from "../api";
import { highlightDiff } from "../utils/highlightDiff";
import { truncateMiddle } from "../utils/truncatePath";
@@ -90,8 +91,8 @@ export function TaskChangesTab({ taskId, worktree, projectId, column, mergeDetai
setExpandedFiles(new Set([normalized[0].path]));
setCurrentFileIndex(0);
}
} catch (err: any) {
setError(err.message || "Failed to load diff");
} catch (err) {
setError(getErrorMessage(err) || "Failed to load diff");
} finally {
setLoading(false);
}

View File

@@ -1,5 +1,6 @@
import { useMemo, useState } from "react";
import type { Task, TaskComment } from "@fusion/core";
import { getErrorMessage } from "@fusion/core";
import { addSteeringComment, updateTaskComment, deleteTaskComment } from "../api";
import type { ToastType } from "../hooks/useToast";
@@ -47,8 +48,8 @@ export function TaskComments({ task, onTaskUpdated, addToast, currentAuthor = "u
setDraft("");
onTaskUpdated?.(updated);
addToast("Comment added", "success");
} catch (error: any) {
addToast(error.message || "Failed to add comment", "error");
} catch (error) {
addToast(getErrorMessage(error) || "Failed to add comment", "error");
} finally {
setSubmitting(false);
}
@@ -64,8 +65,8 @@ export function TaskComments({ task, onTaskUpdated, addToast, currentAuthor = "u
setEditingText("");
onTaskUpdated?.(updated);
addToast("Comment updated", "success");
} catch (error: any) {
addToast(error.message || "Failed to update comment", "error");
} catch (error) {
addToast(getErrorMessage(error) || "Failed to update comment", "error");
} finally {
setSubmitting(false);
}
@@ -77,8 +78,8 @@ export function TaskComments({ task, onTaskUpdated, addToast, currentAuthor = "u
const updated = await deleteTaskComment(task.id, commentId, projectId);
onTaskUpdated?.(updated);
addToast("Comment deleted", "success");
} catch (error: any) {
addToast(error.message || "Failed to delete comment", "error");
} catch (error) {
addToast(getErrorMessage(error) || "Failed to delete comment", "error");
} finally {
setDeletingId(null);
}

View File

@@ -3,7 +3,7 @@ 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, Settings, AgentLogEntry, Agent } from "@fusion/core";
import { COLUMN_LABELS, VALID_TRANSITIONS } from "@fusion/core";
import { COLUMN_LABELS, VALID_TRANSITIONS, getErrorMessage } 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";
import type { ToastType } from "../hooks/useToast";
@@ -371,9 +371,9 @@ export function TaskDetailModal({
.then((results) => {
if (!cancelled) setWorkflowResults(results);
})
.catch((err: any) => {
.catch((err) => {
if (!cancelled) {
addToast(`Failed to load workflow results: ${err.message}`, "error");
addToast(`Failed to load workflow results: ${getErrorMessage(err)}`, "error");
}
})
.finally(() => {
@@ -601,8 +601,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) {
addToast(`Failed to update ${task.id}: ${getErrorMessage(err)}`, "error");
} finally {
if (mountedRef.current) {
setIsSaving(false);
@@ -614,8 +614,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) {
addToast(`Failed to save: ${getErrorMessage(err)}`, "error");
}
}, [task.id, addToast, projectId]);
@@ -671,8 +671,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) {
addToast(getErrorMessage(err), "error");
}
},
[task.id, onMoveTask, onClose, addToast],
@@ -684,8 +684,8 @@ export function TaskDetailModal({
await onDeleteTask(task.id);
onClose();
addToast(`Deleted ${task.id}`, "info");
} catch (err: any) {
addToast(err.message, "error");
} catch (err) {
addToast(getErrorMessage(err), "error");
}
}, [task.id, onDeleteTask, onClose, addToast]);
@@ -700,8 +700,8 @@ export function TaskDetailModal({
: `Closed ${task.id} (${result.error || "no branch to merge"})`;
addToast(msg, "success");
})
.catch((err: any) => {
addToast(err.message, "error");
.catch((err) => {
addToast(getErrorMessage(err), "error");
});
}, [task.id, onMergeTask, onClose, addToast]);
@@ -712,8 +712,8 @@ export function TaskDetailModal({
.then(() => {
addToast(`Retried ${task.id}`, "success");
})
.catch((err: any) => {
addToast(err.message, "error");
.catch((err) => {
addToast(getErrorMessage(err), "error");
});
}, [task.id, onRetryTask, onClose, addToast]);
@@ -724,8 +724,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) {
addToast(getErrorMessage(err), "error");
}
}, [task.id, onDuplicateTask, onClose, addToast]);
@@ -739,8 +739,8 @@ export function TaskDetailModal({
addToast(`Paused ${task.id}`, "success");
}
onClose();
} catch (err: any) {
addToast(err.message, "error");
} catch (err) {
addToast(getErrorMessage(err), "error");
}
}, [task.id, task.paused, onClose, addToast]);
@@ -749,8 +749,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) {
addToast(getErrorMessage(err), "error");
}
}, [task.id, onClose, addToast]);
@@ -760,8 +760,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) {
addToast(getErrorMessage(err), "error");
}
}, [task.id, onClose, addToast]);
@@ -771,8 +771,8 @@ export function TaskDetailModal({
await rebuildTaskSpec(task.id, projectId);
onClose();
addToast(`Respecifying ${task.id}...`, "info");
} catch (err: any) {
addToast(err.message, "error");
} catch (err) {
addToast(getErrorMessage(err), "error");
}
}, [task.id, projectId, onClose, addToast]);
@@ -823,8 +823,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) {
addToast(getErrorMessage(err), "error");
} finally {
setIsRefining(false);
}
@@ -836,8 +836,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) {
addToast(getErrorMessage(err), "error");
} finally {
setUploading(false);
}
@@ -891,8 +891,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) {
addToast(getErrorMessage(err), "error");
}
}, [task.id, addToast]);
@@ -904,9 +904,9 @@ export function TaskDetailModal({
const updatedTask = await updateTask(task.id, { enabledWorkflowSteps }, projectId);
addToast("Workflow steps updated", "success");
onTaskUpdated?.(updatedTask);
} catch (err: any) {
} catch (err) {
setWorkflowEnabledSteps(previousSteps);
addToast(`Failed to update workflow steps: ${err.message}`, "error");
addToast(`Failed to update workflow steps: ${getErrorMessage(err)}`, "error");
}
}, [task.id, projectId, workflowEnabledSteps, onTaskUpdated, addToast]);
@@ -916,8 +916,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) {
addToast(`Failed to load agents: ${getErrorMessage(err)}`, "error");
setShowAgentPicker(false);
} finally {
setAgentsLoading(false);
@@ -936,8 +936,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) {
addToast(`Failed to assign agent: ${getErrorMessage(err)}`, "error");
}
}, [task.id, projectId, agents, onTaskUpdated, addToast]);
@@ -948,8 +948,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) {
addToast(`Failed to unassign agent: ${getErrorMessage(err)}`, "error");
}
}, [task.id, projectId, onTaskUpdated, addToast]);
@@ -958,9 +958,9 @@ export function TaskDetailModal({
setDependencies(newDeps);
try {
await updateTask(task.id, { dependencies: newDeps }, projectId);
} catch (err: any) {
} catch (err) {
setDependencies(dependencies);
addToast(err.message, "error");
addToast(getErrorMessage(err), "error");
}
}, [task.id, dependencies, addToast]);
@@ -970,9 +970,9 @@ export function TaskDetailModal({
setDependencies(newDeps);
try {
await updateTask(task.id, { dependencies: newDeps }, projectId);
} catch (err: any) {
} catch (err) {
setDependencies(dependencies);
addToast(err.message, "error");
addToast(getErrorMessage(err), "error");
}
}, [task.id, dependencies, addToast]);
@@ -995,8 +995,8 @@ export function TaskDetailModal({
if (fullDetail) {
fullDetail.prompt = newContent;
}
} catch (err: any) {
addToast(err.message, "error");
} catch (err) {
addToast(getErrorMessage(err), "error");
throw err;
} finally {
setIsSavingSpec(false);
@@ -1010,11 +1010,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) {
const msg = getErrorMessage(err);
if (msg.includes("in-review") || msg.includes("done")) {
addToast("Cannot request revision: Task must be in 'todo' or 'in-progress' column.", "error");
} else {
addToast(err.message, "error");
addToast(msg, "error");
}
} finally {
setIsRequestingRevision(false);

View File

@@ -3,6 +3,7 @@ import { FileText, ChevronDown, ChevronUp, Plus, Trash2, History } from "lucide-
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import type { Task, TaskDocument, TaskDocumentRevision } from "@fusion/core";
import { getErrorMessage } from "@fusion/core";
import type { ToastType } from "../hooks/useToast";
import {
fetchTaskDocuments,
@@ -61,8 +62,8 @@ export function TaskDocumentsTab({
try {
const docs = await fetchTaskDocuments(taskId, projectId);
setDocuments(docs);
} catch (error: any) {
addToast(error.message || "Failed to load documents", "error");
} catch (error) {
addToast(getErrorMessage(error) || "Failed to load documents", "error");
} finally {
setLoading(false);
}
@@ -102,8 +103,8 @@ export function TaskDocumentsTab({
try {
const revs = await fetchTaskDocumentRevisions(taskId, docKey, projectId);
setRevisions(revs);
} catch (error: any) {
addToast(error.message || "Failed to load revisions", "error");
} catch (error) {
addToast(getErrorMessage(error) || "Failed to load revisions", "error");
} finally {
setLoadingRevisions(false);
}
@@ -136,8 +137,8 @@ export function TaskDocumentsTab({
setExpandedContent(updated.content);
}
addToast("Document saved", "success");
} catch (error: any) {
addToast(error.message || "Failed to save document", "error");
} catch (error) {
addToast(getErrorMessage(error) || "Failed to save document", "error");
} finally {
setSaving(false);
}
@@ -169,8 +170,8 @@ export function TaskDocumentsTab({
setNewDocContent("");
await loadDocuments();
addToast("Document created", "success");
} catch (error: any) {
addToast(error.message || "Failed to create document", "error");
} catch (error) {
addToast(getErrorMessage(error) || "Failed to create document", "error");
} finally {
setSaving(false);
}
@@ -192,8 +193,8 @@ export function TaskDocumentsTab({
}
await loadDocuments();
addToast("Document deleted", "success");
} catch (error: any) {
addToast(error.message || "Failed to delete document", "error");
} catch (error) {
addToast(getErrorMessage(error) || "Failed to delete document", "error");
} finally {
setDeletingKey(null);
}

View File

@@ -454,7 +454,7 @@ export function TaskForm({
descTextareaRef.current.style.height = "auto";
descTextareaRef.current.style.height = descTextareaRef.current.scrollHeight + "px";
}
} catch (err: any) {
} catch (err) {
const errorMessage = getRefineErrorMessage(err);
addToast(errorMessage, "error");
} finally {

View File

@@ -1,4 +1,5 @@
import { useState, useEffect, useRef, useCallback } from "react";
import { getErrorMessage } from "@fusion/core";
import { X, Trash2, Terminal as TerminalIcon, RefreshCw } from "lucide-react";
import { useTerminal } from "../hooks/useTerminal";
import { useTerminalSessions } from "../hooks/useTerminalSessions";
@@ -786,8 +787,8 @@ 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) {
setError(getErrorMessage(err) || "Failed to restart terminal session");
}
}, [restartActiveTab]);

View File

@@ -1,5 +1,6 @@
import { useState, useEffect, useCallback } from "react";
import type { WorkflowStep, WorkflowStepInput, WorkflowStepMode, WorkflowStepPhase } from "@fusion/core";
import { getErrorMessage } from "@fusion/core";
import {
fetchWorkflowSteps,
createWorkflowStep,
@@ -138,8 +139,8 @@ export function WorkflowStepManager({ isOpen, onClose, addToast, projectId }: Wo
setLoading(true);
const data = await fetchWorkflowSteps(projectId);
setSteps(data);
} catch (err: any) {
addToast(err.message || "Failed to load workflow steps", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to load workflow steps", "error");
} finally {
setLoading(false);
}
@@ -168,8 +169,8 @@ export function WorkflowStepManager({ isOpen, onClose, addToast, projectId }: Wo
setTemplatesLoading(true);
const response = await fetchWorkflowStepTemplates();
setTemplates(response.templates);
} catch (err: any) {
addToast(err.message || "Failed to load templates", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to load templates", "error");
} finally {
setTemplatesLoading(false);
}
@@ -261,8 +262,8 @@ export function WorkflowStepManager({ isOpen, onClose, addToast, projectId }: Wo
setEditingId(null);
setForm(EMPTY_FORM);
await loadSteps();
} catch (err: any) {
addToast(err.message || "Failed to save workflow step", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to save workflow step", "error");
} finally {
setSaving(false);
}
@@ -278,8 +279,8 @@ export function WorkflowStepManager({ isOpen, onClose, addToast, projectId }: Wo
setForm(EMPTY_FORM);
}
await loadSteps();
} catch (err: any) {
addToast(err.message || "Failed to delete workflow step", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to delete workflow step", "error");
}
}, [editingId, addToast, loadSteps]);
@@ -320,8 +321,8 @@ export function WorkflowStepManager({ isOpen, onClose, addToast, projectId }: Wo
setForm((prev) => ({ ...prev, prompt: result.prompt }));
addToast("Prompt refined with AI", "success");
await loadSteps();
} catch (err: any) {
addToast(err.message || "Failed to refine prompt", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to refine prompt", "error");
} finally {
setSaving(false);
setRefining(false);
@@ -337,8 +338,8 @@ export function WorkflowStepManager({ isOpen, onClose, addToast, projectId }: Wo
setForm((prev) => ({ ...prev, prompt: result.prompt }));
addToast("Prompt refined with AI", "success");
await loadSteps();
} catch (err: any) {
addToast(err.message || "Failed to refine prompt", "error");
} catch (err) {
addToast(getErrorMessage(err) || "Failed to refine prompt", "error");
} finally {
setRefining(false);
}
@@ -352,11 +353,12 @@ export function WorkflowStepManager({ isOpen, onClose, addToast, projectId }: Wo
await loadSteps();
// Switch to "My Workflow Steps" tab to show the newly added step
setActiveTab("my-steps");
} catch (err: any) {
if (err.message?.includes("already exists")) {
} catch (err) {
const msg = getErrorMessage(err);
if (msg?.includes("already exists")) {
addToast(`A workflow step named '${template.name}' already exists`, "error");
} else {
addToast(err.message || "Failed to add workflow step from template", "error");
addToast(msg || "Failed to add workflow step from template", "error");
}
} finally {
setAddingTemplateId(null);

View File

@@ -1,6 +1,7 @@
import { useCallback, useRef, useState } from "react";
import { fetchBatchStatus } from "../api";
import type { BatchStatusResult } from "@fusion/core";
import { getErrorMessage } from "@fusion/core";
// Module-level store to share batch data across hook instances
const batchBadgeStore = {
@@ -68,11 +69,12 @@ export function useBatchBadgeFetch(projectId?: string): UseBatchBadgeFetchResult
try {
const results = await fetchBatchStatus(taskIds, projectId);
return results;
} catch (err: any) {
} catch (err) {
lastError = err instanceof Error ? err : new Error(String(err));
// If it's a 429 rate limit error, wait before retrying with exponential backoff
if (err?.message?.includes("429") || err?.message?.toLowerCase().includes("rate limit")) {
const errMsg = getErrorMessage(err);
if (errMsg?.includes("429") || errMsg?.toLowerCase().includes("rate limit")) {
const delayMs = Math.min(1000 * Math.pow(2, attempt), 30000); // Max 30s delay
await new Promise((resolve) => setTimeout(resolve, delayMs));
continue;

View File

@@ -1,4 +1,5 @@
import { useState, useEffect, useCallback } from "react";
import { getErrorMessage } from "@fusion/core";
import type { FileNode, FileListResponse } from "../api";
import { fetchFileList } from "../api";
@@ -56,9 +57,9 @@ export function useFileBrowser(taskId: string, enabled: boolean, projectId?: str
if (!cancelled) {
setEntries(response.entries);
}
} catch (err: any) {
} catch (err) {
if (!cancelled) {
setError(err.message || "Failed to load files");
setError(getErrorMessage(err) || "Failed to load files");
setEntries([]);
}
} finally {

View File

@@ -1,4 +1,5 @@
import { useState, useEffect, useCallback } from "react";
import { getErrorMessage } from "@fusion/core";
import type { FileContentResponse, SaveFileResponse } from "../api";
import { fetchFileContent, saveFileContent } from "../api";
@@ -65,9 +66,9 @@ export function useFileEditor(
setOriginalContent(response.content);
setMtime(response.mtime);
}
} catch (err: any) {
} catch (err) {
if (!cancelled) {
setError(err.message || "Failed to load file");
setError(getErrorMessage(err) || "Failed to load file");
setContentState("");
setOriginalContent("");
setMtime(null);
@@ -100,8 +101,8 @@ export function useFileEditor(
const response: SaveFileResponse = await saveFileContent(taskId, filePath, content, projectId);
setOriginalContent(content);
setMtime(response.mtime);
} catch (err: any) {
setError(err.message || "Failed to save file");
} catch (err) {
setError(getErrorMessage(err) || "Failed to save file");
throw err;
} finally {
setSaving(false);

View File

@@ -1,4 +1,5 @@
import { useState, useEffect, useCallback } from "react";
import { getErrorMessage } from "@fusion/core";
import type { FileNode, FileListResponse } from "../api";
import { fetchWorkspaceFileList } from "../api";
@@ -54,9 +55,9 @@ export function useProjectFileBrowser(rootPath: string, enabled: boolean): UsePr
if (!cancelled) {
setEntries(response.entries);
}
} catch (err: any) {
} catch (err) {
if (!cancelled) {
setError(err.message || "Failed to load files");
setError(getErrorMessage(err) || "Failed to load files");
setEntries([]);
}
} finally {

View File

@@ -1,4 +1,5 @@
import { useState, useEffect, useCallback } from "react";
import { getErrorMessage } from "@fusion/core";
import type { FileContentResponse, SaveFileResponse } from "../api";
import { fetchWorkspaceFileContent, saveWorkspaceFileContent } from "../api";
@@ -63,9 +64,9 @@ export function useProjectFileEditor(
setOriginalContent(response.content);
setMtime(response.mtime);
}
} catch (err: any) {
} catch (err) {
if (!cancelled) {
setError(err.message || "Failed to load file");
setError(getErrorMessage(err) || "Failed to load file");
setContentState("");
setOriginalContent("");
setMtime(null);
@@ -98,8 +99,8 @@ export function useProjectFileEditor(
const response: SaveFileResponse = await saveWorkspaceFileContent("project", filePath, content);
setOriginalContent(content);
setMtime(response.mtime);
} catch (err: any) {
setError(err.message || "Failed to save file");
} catch (err) {
setError(getErrorMessage(err) || "Failed to save file");
throw err;
} finally {
setSaving(false);

View File

@@ -1,4 +1,5 @@
import { useState, useEffect, useCallback, useRef } from "react";
import { getErrorMessage } from "@fusion/core";
import { fetchUsageData, type ProviderUsage } from "../api";
interface UsageDataState {
@@ -57,14 +58,14 @@ export function useUsageData(options: UseUsageDataOptions = {}) {
error: null,
lastUpdated: new Date(),
});
} catch (err: any) {
} catch (err) {
// Don't update state if the request was aborted
if (err.name === "AbortError") return;
if (err instanceof Error && err.name === "AbortError") return;
setState((prev) => ({
...prev,
loading: false,
error: err.message || "Failed to fetch usage data",
error: getErrorMessage(err) || "Failed to fetch usage data",
}));
}
}, []);

View File

@@ -1,4 +1,5 @@
import { useState, useEffect, useCallback } from "react";
import { getErrorMessage } from "@fusion/core";
import type { FileNode, FileListResponse } from "../api";
import { fetchWorkspaceFileList } from "../api";
@@ -65,9 +66,9 @@ export function useWorkspaceFileBrowser(
if (!cancelled) {
setEntries(response.entries);
}
} catch (err: any) {
} catch (err) {
if (!cancelled) {
setError(err.message || "Failed to load files");
setError(getErrorMessage(err) || "Failed to load files");
setEntries([]);
}
} finally {

View File

@@ -1,4 +1,5 @@
import { useState, useEffect, useCallback } from "react";
import { getErrorMessage } from "@fusion/core";
import type { FileContentResponse, SaveFileResponse } from "../api";
import { fetchWorkspaceFileContent, saveWorkspaceFileContent } from "../api";
@@ -63,9 +64,9 @@ export function useWorkspaceFileEditor(
setOriginalContent(response.content);
setMtime(response.mtime);
}
} catch (err: any) {
} catch (err) {
if (!cancelled) {
setError(err.message || "Failed to load file");
setError(getErrorMessage(err) || "Failed to load file");
setContentState("");
setOriginalContent("");
setMtime(null);
@@ -98,8 +99,8 @@ export function useWorkspaceFileEditor(
const response: SaveFileResponse = await saveWorkspaceFileContent(workspace, filePath, content, projectId);
setOriginalContent(content);
setMtime(response.mtime);
} catch (err: any) {
setError(err.message || "Failed to save file");
} catch (err) {
setError(getErrorMessage(err) || "Failed to save file");
throw err;
} finally {
setSaving(false);

View File

@@ -1,4 +1,5 @@
import { useEffect, useState } from "react";
import { getErrorMessage } from "@fusion/core";
import { fetchWorkspaces, type WorkspaceTaskInfo } from "../api";
export interface WorkspaceInfo {
@@ -56,9 +57,9 @@ export function useWorkspaces(projectId?: string): UseWorkspacesReturn {
setProjectName(getProjectName(response.project));
setWorkspaces(response.tasks.map(mapTaskWorkspace));
setError(null);
} catch (err: any) {
} catch (err) {
if (!cancelled) {
setError(err.message || "Failed to load workspaces");
setError(getErrorMessage(err) || "Failed to load workspaces");
}
} finally {
if (!cancelled) {