import "./TaskDetailModal.css"; import React, { Suspense, lazy, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { Pencil, Bot, X, ChevronDown, ChevronRight, GitBranch, ArrowLeft, Zap, Loader2 } from "lucide-react"; import { useModalResizePersist } from "../hooks/useModalResizePersist"; import { useMobileScrollLock } from "../hooks/useMobileScrollLock"; import { useOverlayDismiss } from "../hooks/useOverlayDismiss"; import ReactMarkdown from "react-markdown"; import type { Components } from "react-markdown"; import remarkGfm from "remark-gfm"; import type { Task, TaskDetail, TaskAttachment, Column, MergeResult, Settings, GlobalSettings, AgentLogEntry, Agent, TaskPriority, TaskSourceIssue, WorkflowStepResult, GithubIssueAction } from "@fusion/core"; import { COLUMN_LABELS, DEFAULT_TASK_PRIORITY, REPO_OVERRIDE_RE, TASK_PRIORITIES, VALID_TRANSITIONS, getErrorMessage, resolveTaskExecutionModel, resolveTaskPlanningModel, resolveTaskValidatorModel, } from "@fusion/core"; import { uploadAttachment, deleteAttachment, updateTask, pauseTask, unpauseTask, fetchTaskDetail, fetchSettings, fetchGlobalSettings, requestSpecRevision, rebuildTaskSpec, approvePlan, rejectPlan, refineTask, fetchWorkflowResults, assignTask, fetchAgents, fetchAgent } from "../api"; import type { ToastType } from "../hooks/useToast"; import { useAgentLogs } from "../hooks/useAgentLogs"; import { useConfirm } from "../hooks/useConfirm"; import { AgentLogViewer } from "./AgentLogViewer"; import { ModelSelectorTab } from "./ModelSelectorTab"; import { PrSection } from "./PrSection"; import { TaskComments } from "./TaskComments"; import { TaskReviewTab } from "./TaskReviewTab"; import { MergeDetails } from "./MergeDetails"; import { TaskChangesTab } from "./TaskChangesTab"; import { TaskForm, type PendingImage } from "./TaskForm"; import { useNodes } from "../hooks/useNodes"; import { WorkflowResultsTab } from "./WorkflowResultsTab"; import { RoutingTab } from "./RoutingTab"; import { TaskDocumentsTab } from "./TaskDocumentsTab"; import { TaskTokenStatsPanel } from "./TaskTokenStatsPanel"; import { PluginSlot } from "./PluginSlot"; import { ProviderIcon } from "./ProviderIcon"; import { subscribeSse } from "../sse-bus"; import { usePluginUiSlots } from "../hooks/usePluginUiSlots"; import { appendTokenQuery } from "../auth"; import { extractDependencyDeleteConflict } from "../utils/taskDelete"; import { computeBlockerFanoutMap } from "../hooks/useBlockerFanout"; import { resolveEffectiveGithubRepoDefault } from "./githubTracking"; import { linkifyFilePaths, linkifyReactChildren } from "../utils/filePathLinkify"; import { getInReviewStallCopy, shouldShowInReviewStallBadge } from "../utils/inReviewStallCopy"; import { getStalePausedReviewCopy, shouldShowStalePausedReviewBadge } from "../utils/stalePausedReviewCopy"; import { getTaskAgeStalenessCopy } from "../utils/taskAgeStalenessCopy"; import { findInReviewStallLogEntry, IN_REVIEW_STALL_LOG_REGEX } from "../utils/findInReviewStallLogEntry"; interface ModelSelection { provider?: string; modelId?: string; } const ACTIVE_STATUSES = new Set(["planning", "researching", "executing", "finalizing", "merging", "merging-fix"]); const STALE_PAUSED_REVIEW_LOG_REGEX = /^Stale paused review surfaced \[([^\]]+)\]/; const markdownLinkifyComponents: Components = { p: ({ children, ...props }) =>
{linkifyReactChildren(children)}
, li: ({ children, ...props }) =>{children};
}
return {linkedChildren};
},
};
/**
* Resolve the effective executor model following the engine's resolution order:
* 1. Per-task modelProvider/modelId (both must be set)
* 2. Project/global execution lane fallback
*/
function extractExecutorModelFromLog(entries: AgentLogEntry[]): { provider: string; modelId: string } | null {
let result: { provider: string; modelId: string } | null = null;
for (const entry of entries) {
if (entry.agent !== "executor" || entry.type !== "text") continue;
const match = entry.text.match(/^Executor using model: (.+?)\/(.+)$/);
if (match) {
result = { provider: match[1], modelId: match[2] };
}
}
return result;
}
function extractReviewerModelFromLog(entries: AgentLogEntry[]): { provider: string; modelId: string } | null {
let result: { provider: string; modelId: string } | null = null;
for (const entry of entries) {
if (entry.agent !== "reviewer" || entry.type !== "text") continue;
const match = entry.text.match(/^Reviewer using model: (.+?)\/(.+)$/);
if (match) {
result = { provider: match[1], modelId: match[2] };
}
}
return result;
}
function hasUsableTrackingTitle(task: { title?: string | null; description?: string | null }): boolean {
if ((task.title ?? "").trim().length > 0) {
return true;
}
const firstMeaningfulLine = (task.description ?? "")
.split(/\r?\n/)
.map((line) => line.trim())
.find((line) => line.length > 0);
return Boolean(firstMeaningfulLine);
}
function extractAssignedRuntimeModel(agent: Agent | null | undefined): ModelSelection {
const runtimeConfig = (agent?.runtimeConfig ?? undefined) as RecordRetry cap reached for this task.
)}Describe what needs to be refined or improved...