import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; import type { EnvironmentId, ModelSelection, ProviderInteractionMode, RuntimeMode,} from "@t3tools/contracts";import { DEFAULT_PROVIDER_INTERACTION_MODE, DEFAULT_RUNTIME_MODE } from "@t3tools/contracts";import * as Arr from "effect/Array";import { pipe } from "effect/Function"; import type { DraftComposerImageAttachment } from "../../lib/composerImages";import type { ModelOption, ProviderGroup } from "../../lib/modelOptions";import { buildModelOptions, groupByProvider } from "../../lib/modelOptions";import { groupProjectsByRepository } from "../../lib/repositoryGroups";import { scopedProjectKey } from "../../lib/scopedEntities";import { vcsRefManager, useVcsRefs } from "../../state/use-vcs-refs";import { useRemoteCatalog } from "../../state/use-remote-catalog";import { setPendingConnectionError, useRemoteEnvironmentState,} from "../../state/use-remote-environment-registry";import { EnvironmentScopedProjectShell, type VcsRef } from "@t3tools/client-runtime";import type { ClaudeAgentEffort } from "./claudeEffortOptions"; type WorkspaceMode = "local" | "worktree"; function normalizeSelectedWorktreePath( project: EnvironmentScopedProjectShell, branch: VcsRef,): string | null { if (!branch.worktreePath) { return null; } return branch.worktreePath === project.workspaceRoot ? null : branch.worktreePath;} export function branchBadgeLabel(input: { readonly branch: VcsRef; readonly project: EnvironmentScopedProjectShell | null;}): string | null { if (input.branch.current) { return "current"; } if (input.branch.worktreePath && input.branch.worktreePath !== input.project?.workspaceRoot) { return "worktree"; } if (input.branch.isDefault) { return "default"; } if (input.branch.isRemote) { return "remote"; } return null;} type NewTaskFlowContextValue = { readonly logicalProjects: ReadonlyArray<{ readonly key: string; readonly project: EnvironmentScopedProjectShell; }>; readonly selectedEnvironmentId: EnvironmentId | null; readonly selectedProjectKey: string | null; readonly selectedModelKey: string | null; readonly workspaceMode: WorkspaceMode; readonly selectedBranchName: string | null; readonly selectedWorktreePath: string | null; readonly prompt: string; readonly attachments: ReadonlyArray<DraftComposerImageAttachment>; readonly submitting: boolean; readonly branchQuery: string; readonly branchesLoading: boolean; readonly availableBranches: ReadonlyArray<VcsRef>; readonly runtimeMode: RuntimeMode; readonly interactionMode: ProviderInteractionMode; readonly effort: ClaudeAgentEffort; readonly fastMode: boolean; readonly contextWindow: string; readonly expandedProvider: string | null; readonly environments: ReadonlyArray<{ readonly environmentId: EnvironmentId; readonly environmentLabel: string; }>; readonly selectedProject: EnvironmentScopedProjectShell | null; readonly modelOptions: ReadonlyArray<ModelOption>; readonly selectedModel: ModelSelection | null; readonly selectedModelOption: ModelOption | null; readonly providerGroups: ReadonlyArray<ProviderGroup>; readonly filteredBranches: ReadonlyArray<VcsRef>; readonly reset: () => void; readonly setProject: (project: EnvironmentScopedProjectShell) => void; readonly selectEnvironment: (environmentId: EnvironmentId) => void; readonly setSelectedModelKey: (key: string | null) => void; readonly setWorkspaceMode: (mode: WorkspaceMode) => void; readonly selectBranch: (branch: VcsRef) => void; readonly setPrompt: (value: string) => void; readonly replaceAttachments: (attachments: ReadonlyArray<DraftComposerImageAttachment>) => void; readonly appendAttachments: (attachments: ReadonlyArray<DraftComposerImageAttachment>) => void; readonly removeAttachment: (imageId: string) => void; readonly clearAttachments: () => void; readonly setSubmitting: (value: boolean) => void; readonly setBranchQuery: (value: string) => void; readonly loadBranches: () => Promise<void>; readonly setRuntimeMode: (value: RuntimeMode) => void; readonly setInteractionMode: (value: ProviderInteractionMode) => void; readonly setEffort: (value: ClaudeAgentEffort) => void; readonly setFastMode: (value: boolean) => void; readonly setContextWindow: (value: string) => void; readonly setExpandedProvider: (value: string | null) => void;}; const NewTaskFlowContext = React.createContext<NewTaskFlowContextValue | null>(null); export function NewTaskFlowProvider(props: React.PropsWithChildren) { const { projects, serverConfigByEnvironmentId, threads } = useRemoteCatalog(); const { savedConnectionsById } = useRemoteEnvironmentState(); const repositoryGroups = useMemo( () => groupProjectsByRepository({ projects, threads }), [projects, threads], ); const logicalProjects = useMemo( () => pipe( repositoryGroups, Arr.map((group) => { const primaryProject = group.projects[0]?.project; if (!primaryProject) { return null; } return { key: group.key, project: primaryProject }; }), Arr.filter( ( entry, ): entry is { readonly key: string; readonly project: EnvironmentScopedProjectShell; } => entry !== null, ), ), [repositoryGroups], ); const [selectedEnvironmentId, setSelectedEnvironmentId] = useState<EnvironmentId | null>( projects[0]?.environmentId ?? null, ); const [selectedProjectKey, setSelectedProjectKey] = useState<string | null>(null); const [selectedModelKey, setSelectedModelKey] = useState<string | null>(null); const [workspaceMode, setWorkspaceMode] = useState<WorkspaceMode>("local"); const [selectedBranchName, setSelectedBranchName] = useState<string | null>(null); const [selectedWorktreePath, setSelectedWorktreePath] = useState<string | null>(null); const branchLoadVersionRef = useRef(0); const [prompt, setPrompt] = useState(""); const [attachments, setAttachments] = useState<ReadonlyArray<DraftComposerImageAttachment>>([]); const [submitting, setSubmitting] = useState(false); const [branchQuery, setBranchQuery] = useState(""); const [runtimeMode, setRuntimeMode] = useState<RuntimeMode>(DEFAULT_RUNTIME_MODE); const [interactionMode, setInteractionMode] = useState<ProviderInteractionMode>( DEFAULT_PROVIDER_INTERACTION_MODE, ); const [effort, setEffort] = useState<ClaudeAgentEffort>("high"); const [fastMode, setFastMode] = useState(false); const [contextWindow, setContextWindow] = useState("1M"); const [expandedProvider, setExpandedProvider] = useState<string | null>(null); const replaceAttachments = useCallback( (nextAttachments: ReadonlyArray<DraftComposerImageAttachment>) => { setAttachments(nextAttachments); }, [], ); const appendAttachments = useCallback( (nextAttachments: ReadonlyArray<DraftComposerImageAttachment>) => { setAttachments((current) => [...current, ...nextAttachments]); }, [], ); const removeAttachment = useCallback((imageId: string) => { setAttachments((current) => current.filter((candidate) => candidate.id !== imageId)); }, []); const clearAttachments = useCallback(() => { setAttachments([]); }, []); const reset = useCallback(() => { console.log("[new task flow] reset", { defaultEnvironmentId: projects[0]?.environmentId ?? null, projectCount: projects.length, }); setSelectedEnvironmentId(projects[0]?.environmentId ?? null); setSelectedProjectKey(null); setSelectedModelKey(null); setWorkspaceMode("local"); setSelectedBranchName(null); setSelectedWorktreePath(null); setPrompt(""); clearAttachments(); setSubmitting(false); setBranchQuery(""); setRuntimeMode(DEFAULT_RUNTIME_MODE); setInteractionMode(DEFAULT_PROVIDER_INTERACTION_MODE); setEffort("high"); setFastMode(false); setContextWindow("1M"); setExpandedProvider(null); }, [clearAttachments, projects]); useEffect(() => { if (selectedEnvironmentId !== null || projects.length === 0) { return; } console.log("[new task flow] initializing environment", { environmentId: projects[0]!.environmentId, }); setSelectedEnvironmentId(projects[0]!.environmentId); }, [projects, selectedEnvironmentId]); const environments = useMemo( () => pipe( [ ...new Set( pipe( projects, Arr.map((project) => project.environmentId), ), ), ], Arr.map((environmentId) => { const environment = savedConnectionsById[environmentId]; if (!environment) { return null; } return { environmentId, environmentLabel: environment.environmentLabel, }; }), Arr.filter( ( entry, ): entry is { readonly environmentId: EnvironmentId; readonly environmentLabel: string; } => entry !== null, ), ), [projects, savedConnectionsById], ); const projectsForEnvironment = useMemo( () => pipe( projects, Arr.filter((project) => project.environmentId === selectedEnvironmentId), ), [projects, selectedEnvironmentId], ); const selectedProject = projectsForEnvironment.find( (project) => scopedProjectKey(project.environmentId, project.id) === selectedProjectKey, ) ?? projectsForEnvironment[0] ?? null; const modelOptions = useMemo( () => buildModelOptions( selectedProject ? (serverConfigByEnvironmentId[selectedProject.environmentId] ?? null) : null, selectedProject?.defaultModelSelection ?? null, ), [selectedProject, serverConfigByEnvironmentId], ); const selectedModel = modelOptions.find((option) => option.key === selectedModelKey)?.selection ?? selectedProject?.defaultModelSelection ?? modelOptions[0]?.selection ?? null; const selectedModelOption = modelOptions.find( (option) => selectedModel && option.selection.instanceId === selectedModel.instanceId && option.selection.model === selectedModel.model, ) ?? null; const providerGroups = useMemo(() => groupByProvider(modelOptions), [modelOptions]); const branchTarget = useMemo( () => ({ environmentId: selectedProject?.environmentId ?? null, cwd: selectedProject?.workspaceRoot ?? null, query: null, }), [selectedProject?.environmentId, selectedProject?.workspaceRoot], ); const branchState = useVcsRefs(branchTarget); const branchesLoading = branchState.isPending; const availableBranches = useMemo( () => pipe( branchState.data?.refs ?? [], Arr.filter((branch) => !branch.isRemote), ), [branchState.data?.refs], ); const filteredBranches = useMemo(() => { const query = branchQuery.trim().toLowerCase(); if (query.length === 0) { return availableBranches; } return pipe( availableBranches, Arr.filter((branch) => branch.name.toLowerCase().includes(query)), ); }, [availableBranches, branchQuery]); const setProject = useCallback((project: EnvironmentScopedProjectShell) => { const nextProjectKey = scopedProjectKey(project.environmentId, project.id); branchLoadVersionRef.current += 1; setSelectedEnvironmentId(project.environmentId); setSelectedProjectKey(nextProjectKey); setSelectedBranchName(null); setSelectedWorktreePath(null); }, []); const selectEnvironment = useCallback((environmentId: EnvironmentId) => { branchLoadVersionRef.current += 1; setSelectedEnvironmentId(environmentId); setSelectedProjectKey(null); setSelectedBranchName(null); setSelectedWorktreePath(null); }, []); const selectBranch = useCallback( (branch: VcsRef) => { setSelectedBranchName(branch.name); setSelectedWorktreePath( selectedProject ? normalizeSelectedWorktreePath(selectedProject, branch) : null, ); }, [selectedProject], ); const loadBranches = useCallback(async () => { if (!selectedProject) { return; } const loadVersion = ++branchLoadVersionRef.current; const projectKey = scopedProjectKey(selectedProject.environmentId, selectedProject.id); try { const result = await vcsRefManager.load({ environmentId: selectedProject.environmentId, cwd: selectedProject.workspaceRoot, query: null, }); if (loadVersion !== branchLoadVersionRef.current || selectedProjectKey !== projectKey) { return; } setPendingConnectionError(null); const branches = pipe( result?.refs ?? [], Arr.filter((branch) => !branch.isRemote), ); if (workspaceMode === "worktree" && !selectedBranchName) { const preferredBranch = branches.find((branch) => branch.current)?.name ?? branches.find((branch) => branch.isDefault)?.name ?? null; if (preferredBranch) { setSelectedBranchName(preferredBranch); } } } catch { if (loadVersion !== branchLoadVersionRef.current) { return; } setPendingConnectionError("Failed to load branches."); } }, [selectedBranchName, selectedProject, selectedProjectKey, workspaceMode]); const value = useMemo<NewTaskFlowContextValue>( () => ({ logicalProjects, selectedEnvironmentId, selectedProjectKey, selectedModelKey, workspaceMode, selectedBranchName, selectedWorktreePath, prompt, attachments, submitting, branchQuery, branchesLoading, availableBranches, runtimeMode, interactionMode, effort, fastMode, contextWindow, expandedProvider, environments, selectedProject, modelOptions, selectedModel, selectedModelOption, providerGroups, filteredBranches, reset, setProject, selectEnvironment, setSelectedModelKey, setWorkspaceMode, selectBranch, setPrompt, replaceAttachments, appendAttachments, removeAttachment, clearAttachments, setSubmitting, setBranchQuery, loadBranches, setRuntimeMode, setInteractionMode, setEffort, setFastMode, setContextWindow, setExpandedProvider, }), [ attachments, availableBranches, branchQuery, branchesLoading, contextWindow, effort, environments, expandedProvider, fastMode, filteredBranches, interactionMode, loadBranches, logicalProjects, modelOptions, prompt, providerGroups, replaceAttachments, reset, runtimeMode, selectedBranchName, selectedEnvironmentId, selectedModel, selectedModelKey, selectedModelOption, selectedProject, selectedProjectKey, selectedWorktreePath, setProject, selectBranch, selectEnvironment, submitting, workspaceMode, appendAttachments, clearAttachments, removeAttachment, ], ); useEffect(() => { console.log("[new task flow] state", { availableBranchCount: availableBranches.length, environmentCount: environments.length, logicalProjectCount: logicalProjects.length, selectedEnvironmentId, selectedProjectKey, selectedProjectTitle: selectedProject?.title ?? null, }); }, [ availableBranches.length, environments.length, logicalProjects.length, selectedEnvironmentId, selectedProject?.title,Lines 1–500 of 513. Load more
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; import type { EnvironmentId, ModelSelection, ProviderInteractionMode, RuntimeMode, } from "@t3tools/contracts"; import { DEFAULT_PROVIDER_INTERACTION_MODE, DEFAULT_RUNTIME_MODE } from "@t3tools/contracts"; import * as Arr from "effect/Array"; import { pipe } from "effect/Function"; import type { DraftComposerImageAttachment } from "../../lib/composerImages"; import type { ModelOption, ProviderGroup } from "../../lib/modelOptions"; import { buildModelOptions, groupByProvider } from "../../lib/modelOptions"; import { groupProjectsByRepository } from "../../lib/repositoryGroups"; import { scopedProjectKey } from "../../lib/scopedEntities"; import { vcsRefManager, useVcsRefs } from "../../state/use-vcs-refs"; import { useRemoteCatalog } from "../../state/use-remote-catalog"; import { setPendingConnectionError, useRemoteEnvironmentState, } from "../../state/use-remote-environment-registry"; import { EnvironmentScopedProjectShell, type VcsRef } from "@t3tools/client-runtime"; import type { ClaudeAgentEffort } from "./claudeEffortOptions"; type WorkspaceMode = "local" | "worktree"; function normalizeSelectedWorktreePath( project: EnvironmentScopedProjectShell, branch: VcsRef, ): string | null { if (!branch.worktreePath) { return null; } return branch.worktreePath === project.workspaceRoot ? null : branch.worktreePath; } export function branchBadgeLabel(input: { readonly branch: VcsRef; readonly project: EnvironmentScopedProjectShell | null; }): string | null { if (input.branch.current) { return "current"; } if (input.branch.worktreePath && input.branch.worktreePath !== input.project?.workspaceRoot) { return "worktree"; } if (input.branch.isDefault) { return "default"; } if (input.branch.isRemote) { return "remote"; } return null; } type NewTaskFlowContextValue = { readonly logicalProjects: ReadonlyArray<{ readonly key: string; readonly project: EnvironmentScopedProjectShell; }>; readonly selectedEnvironmentId: EnvironmentId | null; readonly selectedProjectKey: string | null; readonly selectedModelKey: string | null; readonly workspaceMode: WorkspaceMode; readonly selectedBranchName: string | null; readonly selectedWorktreePath: string | null; readonly prompt: string; readonly attachments: ReadonlyArray<DraftComposerImageAttachment>; readonly submitting: boolean; readonly branchQuery: string; readonly branchesLoading: boolean; readonly availableBranches: ReadonlyArray<VcsRef>; readonly runtimeMode: RuntimeMode; readonly interactionMode: ProviderInteractionMode; readonly effort: ClaudeAgentEffort; readonly fastMode: boolean; readonly contextWindow: string; readonly expandedProvider: string | null; readonly environments: ReadonlyArray<{ readonly environmentId: EnvironmentId; readonly environmentLabel: string; }>; readonly selectedProject: EnvironmentScopedProjectShell | null; readonly modelOptions: ReadonlyArray<ModelOption>; readonly selectedModel: ModelSelection | null; readonly selectedModelOption: ModelOption | null; readonly providerGroups: ReadonlyArray<ProviderGroup>; readonly filteredBranches: ReadonlyArray<VcsRef>; readonly reset: () => void; readonly setProject: (project: EnvironmentScopedProjectShell) => void; readonly selectEnvironment: (environmentId: EnvironmentId) => void; readonly setSelectedModelKey: (key: string | null) => void; readonly setWorkspaceMode: (mode: WorkspaceMode) => void; readonly selectBranch: (branch: VcsRef) => void; readonly setPrompt: (value: string) => void; readonly replaceAttachments: (attachments: ReadonlyArray<DraftComposerImageAttachment>) => void; readonly appendAttachments: (attachments: ReadonlyArray<DraftComposerImageAttachment>) => void; readonly removeAttachment: (imageId: string) => void; readonly clearAttachments: () => void; readonly setSubmitting: (value: boolean) => void; readonly setBranchQuery: (value: string) => void; readonly loadBranches: () => Promise<void>; readonly setRuntimeMode: (value: RuntimeMode) => void; readonly setInteractionMode: (value: ProviderInteractionMode) => void; readonly setEffort: (value: ClaudeAgentEffort) => void; readonly setFastMode: (value: boolean) => void; readonly setContextWindow: (value: string) => void; readonly setExpandedProvider: (value: string | null) => void; }; const NewTaskFlowContext = React.createContext<NewTaskFlowContextValue | null>(null); export function NewTaskFlowProvider(props: React.PropsWithChildren) { const { projects, serverConfigByEnvironmentId, threads } = useRemoteCatalog(); const { savedConnectionsById } = useRemoteEnvironmentState(); const repositoryGroups = useMemo( () => groupProjectsByRepository({ projects, threads }), [projects, threads], ); const logicalProjects = useMemo( () => pipe( repositoryGroups, Arr.map((group) => { const primaryProject = group.projects[0]?.project; if (!primaryProject) { return null; } return { key: group.key, project: primaryProject }; }), Arr.filter( ( entry, ): entry is { readonly key: string; readonly project: EnvironmentScopedProjectShell; } => entry !== null, ), ), [repositoryGroups], ); const [selectedEnvironmentId, setSelectedEnvironmentId] = useState<EnvironmentId | null>( projects[0]?.environmentId ?? null, ); const [selectedProjectKey, setSelectedProjectKey] = useState<string | null>(null); const [selectedModelKey, setSelectedModelKey] = useState<string | null>(null); const [workspaceMode, setWorkspaceMode] = useState<WorkspaceMode>("local"); const [selectedBranchName, setSelectedBranchName] = useState<string | null>(null); const [selectedWorktreePath, setSelectedWorktreePath] = useState<string | null>(null); const branchLoadVersionRef = useRef(0); const [prompt, setPrompt] = useState(""); const [attachments, setAttachments] = useState<ReadonlyArray<DraftComposerImageAttachment>>([]); const [submitting, setSubmitting] = useState(false); const [branchQuery, setBranchQuery] = useState(""); const [runtimeMode, setRuntimeMode] = useState<RuntimeMode>(DEFAULT_RUNTIME_MODE); const [interactionMode, setInteractionMode] = useState<ProviderInteractionMode>( DEFAULT_PROVIDER_INTERACTION_MODE, ); const [effort, setEffort] = useState<ClaudeAgentEffort>("high"); const [fastMode, setFastMode] = useState(false); const [contextWindow, setContextWindow] = useState("1M"); const [expandedProvider, setExpandedProvider] = useState<string | null>(null); const replaceAttachments = useCallback( (nextAttachments: ReadonlyArray<DraftComposerImageAttachment>) => { setAttachments(nextAttachments); }, [], ); const appendAttachments = useCallback( (nextAttachments: ReadonlyArray<DraftComposerImageAttachment>) => { setAttachments((current) => [...current, ...nextAttachments]); }, [], ); const removeAttachment = useCallback((imageId: string) => { setAttachments((current) => current.filter((candidate) => candidate.id !== imageId)); }, []); const clearAttachments = useCallback(() => { setAttachments([]); }, []); const reset = useCallback(() => { console.log("[new task flow] reset", { defaultEnvironmentId: projects[0]?.environmentId ?? null, projectCount: projects.length, }); setSelectedEnvironmentId(projects[0]?.environmentId ?? null); setSelectedProjectKey(null); setSelectedModelKey(null); setWorkspaceMode("local"); setSelectedBranchName(null); setSelectedWorktreePath(null); setPrompt(""); clearAttachments(); setSubmitting(false); setBranchQuery(""); setRuntimeMode(DEFAULT_RUNTIME_MODE); setInteractionMode(DEFAULT_PROVIDER_INTERACTION_MODE); setEffort("high"); setFastMode(false); setContextWindow("1M"); setExpandedProvider(null); }, [clearAttachments, projects]); useEffect(() => { if (selectedEnvironmentId !== null || projects.length === 0) { return; } console.log("[new task flow] initializing environment", { environmentId: projects[0]!.environmentId, }); setSelectedEnvironmentId(projects[0]!.environmentId); }, [projects, selectedEnvironmentId]); const environments = useMemo( () => pipe( [ ...new Set( pipe( projects, Arr.map((project) => project.environmentId), ), ), ], Arr.map((environmentId) => { const environment = savedConnectionsById[environmentId]; if (!environment) { return null; } return { environmentId, environmentLabel: environment.environmentLabel, }; }), Arr.filter( ( entry, ): entry is { readonly environmentId: EnvironmentId; readonly environmentLabel: string; } => entry !== null, ), ), [projects, savedConnectionsById], ); const projectsForEnvironment = useMemo( () => pipe( projects, Arr.filter((project) => project.environmentId === selectedEnvironmentId), ), [projects, selectedEnvironmentId], ); const selectedProject = projectsForEnvironment.find( (project) => scopedProjectKey(project.environmentId, project.id) === selectedProjectKey, ) ?? projectsForEnvironment[0] ?? null; const modelOptions = useMemo( () => buildModelOptions( selectedProject ? (serverConfigByEnvironmentId[selectedProject.environmentId] ?? null) : null, selectedProject?.defaultModelSelection ?? null, ), [selectedProject, serverConfigByEnvironmentId], ); const selectedModel = modelOptions.find((option) => option.key === selectedModelKey)?.selection ?? selectedProject?.defaultModelSelection ?? modelOptions[0]?.selection ?? null; const selectedModelOption = modelOptions.find( (option) => selectedModel && option.selection.instanceId === selectedModel.instanceId && option.selection.model === selectedModel.model, ) ?? null; const providerGroups = useMemo(() => groupByProvider(modelOptions), [modelOptions]); const branchTarget = useMemo( () => ({ environmentId: selectedProject?.environmentId ?? null, cwd: selectedProject?.workspaceRoot ?? null, query: null, }), [selectedProject?.environmentId, selectedProject?.workspaceRoot], ); const branchState = useVcsRefs(branchTarget); const branchesLoading = branchState.isPending; const availableBranches = useMemo( () => pipe( branchState.data?.refs ?? [], Arr.filter((branch) => !branch.isRemote), ), [branchState.data?.refs], ); const filteredBranches = useMemo(() => { const query = branchQuery.trim().toLowerCase(); if (query.length === 0) { return availableBranches; } return pipe( availableBranches, Arr.filter((branch) => branch.name.toLowerCase().includes(query)), ); }, [availableBranches, branchQuery]); const setProject = useCallback((project: EnvironmentScopedProjectShell) => { const nextProjectKey = scopedProjectKey(project.environmentId, project.id); branchLoadVersionRef.current += 1; setSelectedEnvironmentId(project.environmentId); setSelectedProjectKey(nextProjectKey); setSelectedBranchName(null); setSelectedWorktreePath(null); }, []); const selectEnvironment = useCallback((environmentId: EnvironmentId) => { branchLoadVersionRef.current += 1; setSelectedEnvironmentId(environmentId); setSelectedProjectKey(null); setSelectedBranchName(null); setSelectedWorktreePath(null); }, []); const selectBranch = useCallback( (branch: VcsRef) => { setSelectedBranchName(branch.name); setSelectedWorktreePath( selectedProject ? normalizeSelectedWorktreePath(selectedProject, branch) : null, ); }, [selectedProject], ); const loadBranches = useCallback(async () => { if (!selectedProject) { return; } const loadVersion = ++branchLoadVersionRef.current; const projectKey = scopedProjectKey(selectedProject.environmentId, selectedProject.id); try { const result = await vcsRefManager.load({ environmentId: selectedProject.environmentId, cwd: selectedProject.workspaceRoot, query: null, }); if (loadVersion !== branchLoadVersionRef.current || selectedProjectKey !== projectKey) { return; } setPendingConnectionError(null); const branches = pipe( result?.refs ?? [], Arr.filter((branch) => !branch.isRemote), ); if (workspaceMode === "worktree" && !selectedBranchName) { const preferredBranch = branches.find((branch) => branch.current)?.name ?? branches.find((branch) => branch.isDefault)?.name ?? null; if (preferredBranch) { setSelectedBranchName(preferredBranch); } } } catch { if (loadVersion !== branchLoadVersionRef.current) { return; } setPendingConnectionError("Failed to load branches."); } }, [selectedBranchName, selectedProject, selectedProjectKey, workspaceMode]); const value = useMemo<NewTaskFlowContextValue>( () => ({ logicalProjects, selectedEnvironmentId, selectedProjectKey, selectedModelKey, workspaceMode, selectedBranchName, selectedWorktreePath, prompt, attachments, submitting, branchQuery, branchesLoading, availableBranches, runtimeMode, interactionMode, effort, fastMode, contextWindow, expandedProvider, environments, selectedProject, modelOptions, selectedModel, selectedModelOption, providerGroups, filteredBranches, reset, setProject, selectEnvironment, setSelectedModelKey, setWorkspaceMode, selectBranch, setPrompt, replaceAttachments, appendAttachments, removeAttachment, clearAttachments, setSubmitting, setBranchQuery, loadBranches, setRuntimeMode, setInteractionMode, setEffort, setFastMode, setContextWindow, setExpandedProvider, }), [ attachments, availableBranches, branchQuery, branchesLoading, contextWindow, effort, environments, expandedProvider, fastMode, filteredBranches, interactionMode, loadBranches, logicalProjects, modelOptions, prompt, providerGroups, replaceAttachments, reset, runtimeMode, selectedBranchName, selectedEnvironmentId, selectedModel, selectedModelKey, selectedModelOption, selectedProject, selectedProjectKey, selectedWorktreePath, setProject, selectBranch, selectEnvironment, submitting, workspaceMode, appendAttachments, clearAttachments, removeAttachment, ], ); useEffect(() => { console.log("[new task flow] state", { availableBranchCount: availableBranches.length, environmentCount: environments.length, logicalProjectCount: logicalProjects.length, selectedEnvironmentId, selectedProjectKey, selectedProjectTitle: selectedProject?.title ?? null, }); }, [ availableBranches.length, environments.length, logicalProjects.length, selectedEnvironmentId, selectedProject?.title,