From bb60bbb0ecbc39dae8ce4ededc07f47526c768db Mon Sep 17 00:00:00 2001 From: 777genius Date: Thu, 16 Apr 2026 22:52:56 +0300 Subject: [PATCH] fix(ci): restore workspace validation --- .../renderer/hooks/useGraphActivityContext.ts | 2 +- .../renderer/hooks/useTeamGraphAdapter.ts | 4 +- .../hooks/useTeamGraphSurfaceActions.ts | 10 ++++ .../renderer/ui/TeamGraphOverlay.tsx | 18 +++--- .../agent-graph/renderer/ui/TeamGraphTab.tsx | 14 ++--- .../input/http/registerRecentProjectsHttp.ts | 2 +- .../createRecentProjectsFeature.ts | 9 +-- .../utils/recentProjectsClientCache.ts | 9 +-- src/main/ipc/teams.ts | 2 +- .../infrastructure/CliInstallerService.ts | 4 +- src/main/services/team/TeamDataService.ts | 4 +- src/main/services/team/TeamMemberResolver.ts | 2 +- .../services/team/TeamMessageFeedService.ts | 5 +- .../services/team/TeamProvisioningService.ts | 8 +-- src/preload/index.ts | 4 +- src/renderer/api/httpClient.ts | 2 +- .../components/dashboard/CliStatusBanner.tsx | 2 +- .../extensions/plugins/PluginCard.tsx | 2 +- .../extensions/plugins/PluginDetailDialog.tsx | 2 +- .../runtime/ProviderModelBadges.tsx | 8 +-- .../settings/sections/CliStatusSection.tsx | 2 +- src/renderer/components/team/TeamListView.tsx | 2 +- .../team/dialogs/CreateTeamDialog.tsx | 17 +++--- .../team/dialogs/LaunchTeamDialog.tsx | 17 +++--- .../team/dialogs/TeamModelSelector.tsx | 2 +- .../team/dialogs/launchDialogPrefill.ts | 4 +- .../dialogs/providerPrepareDiagnostics.ts | 22 ++++--- .../team/members/MemberDraftRow.tsx | 2 +- .../components/team/members/MemberList.tsx | 2 +- .../team/members/membersEditorUtils.ts | 6 +- .../team/messages/MessagesPanel.tsx | 60 ++++--------------- src/renderer/store/slices/extensionsSlice.ts | 7 ++- src/renderer/store/slices/teamSlice.ts | 11 ++-- src/renderer/utils/memberRuntimeSummary.ts | 2 +- src/renderer/utils/teamModelAvailability.ts | 49 ++++++++------- src/renderer/utils/teamModelCatalog.ts | 3 +- src/shared/types/api.ts | 4 +- src/shared/utils/extensionNormalizers.ts | 2 +- .../CliInstallerService.test.ts | 43 +++++++++---- ...liProviderModelAvailabilityService.test.ts | 10 ++-- .../services/team/TeammateToolTracker.test.ts | 19 ++++-- .../team/members/MemberCard.test.ts | 10 ++-- 42 files changed, 208 insertions(+), 201 deletions(-) diff --git a/src/features/agent-graph/renderer/hooks/useGraphActivityContext.ts b/src/features/agent-graph/renderer/hooks/useGraphActivityContext.ts index c633062c..84cc991c 100644 --- a/src/features/agent-graph/renderer/hooks/useGraphActivityContext.ts +++ b/src/features/agent-graph/renderer/hooks/useGraphActivityContext.ts @@ -6,8 +6,8 @@ import { } from '@renderer/store/slices/teamSlice'; import { useShallow } from 'zustand/react/shallow'; -import type { TeamSummary } from '@shared/types/team'; import type { TeamGraphData } from '../adapters/TeamGraphAdapter'; +import type { TeamSummary } from '@shared/types/team'; export function useGraphActivityContext(teamName: string): { teamData: TeamGraphData | null; diff --git a/src/features/agent-graph/renderer/hooks/useTeamGraphAdapter.ts b/src/features/agent-graph/renderer/hooks/useTeamGraphAdapter.ts index 8400999b..8e341bc2 100644 --- a/src/features/agent-graph/renderer/hooks/useTeamGraphAdapter.ts +++ b/src/features/agent-graph/renderer/hooks/useTeamGraphAdapter.ts @@ -9,8 +9,8 @@ import { getSnapshot, subscribe } from '@renderer/services/commentReadStorage'; import { useStore } from '@renderer/store'; import { getCurrentProvisioningProgressForTeam, - selectResolvedMembersForTeamName, isTeamGraphSlotPersistenceDisabled, + selectResolvedMembersForTeamName, selectTeamDataForName, selectTeamMessages, } from '@renderer/store/slices/teamSlice'; @@ -19,8 +19,8 @@ import { useShallow } from 'zustand/react/shallow'; import { TeamGraphAdapter } from '../adapters/TeamGraphAdapter'; -import type { GraphDataPort } from '@claude-teams/agent-graph'; import type { TeamGraphData } from '../adapters/TeamGraphAdapter'; +import type { GraphDataPort } from '@claude-teams/agent-graph'; export function useTeamGraphAdapter(teamName: string): GraphDataPort { const adapterRef = useRef(TeamGraphAdapter.create()); diff --git a/src/features/agent-graph/renderer/hooks/useTeamGraphSurfaceActions.ts b/src/features/agent-graph/renderer/hooks/useTeamGraphSurfaceActions.ts index ed30a998..b7565a16 100644 --- a/src/features/agent-graph/renderer/hooks/useTeamGraphSurfaceActions.ts +++ b/src/features/agent-graph/renderer/hooks/useTeamGraphSurfaceActions.ts @@ -1,6 +1,7 @@ import { useCallback } from 'react'; import { useStore } from '@renderer/store'; +import { isTeamGraphSlotPersistenceDisabled } from '@renderer/store/slices/teamSlice'; import { parseGraphMemberNodeId } from '../../core/domain/graphOwnerIdentity'; @@ -8,6 +9,7 @@ import type { GraphOwnerSlotAssignment } from '@claude-teams/agent-graph'; export function useTeamGraphSurfaceActions(teamName: string): { openTeamPage: () => void; + resetOwnerSlotAssignmentsToDefaults: () => void; commitOwnerSlotDrop: (payload: { nodeId: string; assignment: GraphOwnerSlotAssignment; @@ -19,6 +21,13 @@ export function useTeamGraphSurfaceActions(teamName: string): { useStore.getState().openTeamTab(teamName); }, [teamName]); + const resetOwnerSlotAssignmentsToDefaults = useCallback(() => { + if (!isTeamGraphSlotPersistenceDisabled()) { + return; + } + useStore.getState().resetTeamGraphSlotAssignmentsToDefaults(teamName); + }, [teamName]); + const commitOwnerSlotDrop = useCallback( (payload: { nodeId: string; @@ -51,6 +60,7 @@ export function useTeamGraphSurfaceActions(teamName: string): { return { openTeamPage, + resetOwnerSlotAssignmentsToDefaults, commitOwnerSlotDrop, }; } diff --git a/src/features/agent-graph/renderer/ui/TeamGraphOverlay.tsx b/src/features/agent-graph/renderer/ui/TeamGraphOverlay.tsx index 9780bef1..ba700bb1 100644 --- a/src/features/agent-graph/renderer/ui/TeamGraphOverlay.tsx +++ b/src/features/agent-graph/renderer/ui/TeamGraphOverlay.tsx @@ -7,8 +7,6 @@ import { useCallback, useLayoutEffect, useMemo } from 'react'; import { GraphView } from '@claude-teams/agent-graph'; import { TeamSidebarHost } from '@renderer/components/team/sidebar/TeamSidebarHost'; -import { useStore } from '@renderer/store'; -import { isTeamGraphSlotPersistenceDisabled } from '@renderer/store/slices/teamSlice'; import { useGraphCreateTaskDialog } from '../hooks/useGraphCreateTaskDialog'; import { useGraphSidebarVisibility } from '../hooks/useGraphSidebarVisibility'; @@ -54,10 +52,11 @@ export const TeamGraphOverlay = ({ onOpenMemberProfile, }: TeamGraphOverlayProps): React.JSX.Element => { const graphData = useTeamGraphAdapter(teamName); - const { openTeamPage: openTeamTab, commitOwnerSlotDrop } = useTeamGraphSurfaceActions(teamName); - const resetTeamGraphSlotAssignmentsToDefaults = useStore( - (s) => s.resetTeamGraphSlotAssignmentsToDefaults - ); + const { + openTeamPage: openTeamTab, + resetOwnerSlotAssignmentsToDefaults, + commitOwnerSlotDrop, + } = useTeamGraphSurfaceActions(teamName); const { sidebarVisible: persistedSidebarVisible, toggleSidebarVisible } = useGraphSidebarVisibility(); const { dialog: createTaskDialog, openCreateTaskDialog } = useGraphCreateTaskDialog(teamName); @@ -92,11 +91,8 @@ export const TeamGraphOverlay = ({ }, [openCreateTaskDialog]); useLayoutEffect(() => { - if (!isTeamGraphSlotPersistenceDisabled()) { - return; - } - resetTeamGraphSlotAssignmentsToDefaults(teamName); - }, [resetTeamGraphSlotAssignmentsToDefaults, teamName]); + resetOwnerSlotAssignmentsToDefaults(); + }, [resetOwnerSlotAssignmentsToDefaults]); const events: GraphEventPort = { onNodeDoubleClick: useCallback( diff --git a/src/features/agent-graph/renderer/ui/TeamGraphTab.tsx b/src/features/agent-graph/renderer/ui/TeamGraphTab.tsx index a5e8ec10..2fdaf1b3 100644 --- a/src/features/agent-graph/renderer/ui/TeamGraphTab.tsx +++ b/src/features/agent-graph/renderer/ui/TeamGraphTab.tsx @@ -7,8 +7,6 @@ import { lazy, Suspense, useCallback, useLayoutEffect, useMemo, useState } from import { GraphView } from '@claude-teams/agent-graph'; import { TeamSidebarHost } from '@renderer/components/team/sidebar/TeamSidebarHost'; -import { useStore } from '@renderer/store'; -import { isTeamGraphSlotPersistenceDisabled } from '@renderer/store/slices/teamSlice'; import { useGraphCreateTaskDialog } from '../hooks/useGraphCreateTaskDialog'; import { useGraphSidebarVisibility } from '../hooks/useGraphSidebarVisibility'; @@ -47,10 +45,8 @@ export const TeamGraphTab = ({ isPaneFocused = false, }: TeamGraphTabProps): React.JSX.Element => { const graphData = useTeamGraphAdapter(teamName); - const { openTeamPage, commitOwnerSlotDrop } = useTeamGraphSurfaceActions(teamName); - const resetTeamGraphSlotAssignmentsToDefaults = useStore( - (s) => s.resetTeamGraphSlotAssignmentsToDefaults - ); + const { openTeamPage, resetOwnerSlotAssignmentsToDefaults, commitOwnerSlotDrop } = + useTeamGraphSurfaceActions(teamName); const [fullscreen, setFullscreen] = useState(false); const { sidebarVisible, toggleSidebarVisible } = useGraphSidebarVisibility(); const { dialog: createTaskDialog, openCreateTaskDialog } = useGraphCreateTaskDialog(teamName); @@ -82,11 +78,11 @@ export const TeamGraphTab = ({ }, [openCreateTaskDialog]); useLayoutEffect(() => { - if (!isTeamGraphSlotPersistenceDisabled() || !isActive) { + if (!isActive) { return; } - resetTeamGraphSlotAssignmentsToDefaults(teamName); - }, [isActive, resetTeamGraphSlotAssignmentsToDefaults, teamName]); + resetOwnerSlotAssignmentsToDefaults(); + }, [isActive, resetOwnerSlotAssignmentsToDefaults]); // Task action dispatchers const dispatchTaskAction = useCallback( diff --git a/src/features/recent-projects/main/adapters/input/http/registerRecentProjectsHttp.ts b/src/features/recent-projects/main/adapters/input/http/registerRecentProjectsHttp.ts index ac3001f0..104ccb1a 100644 --- a/src/features/recent-projects/main/adapters/input/http/registerRecentProjectsHttp.ts +++ b/src/features/recent-projects/main/adapters/input/http/registerRecentProjectsHttp.ts @@ -1,7 +1,7 @@ import { DASHBOARD_RECENT_PROJECTS_ROUTE, - normalizeDashboardRecentProjectsPayload, type DashboardRecentProjectsPayload, + normalizeDashboardRecentProjectsPayload, } from '@features/recent-projects/contracts'; import { createLogger } from '@shared/utils/logger'; diff --git a/src/features/recent-projects/main/composition/createRecentProjectsFeature.ts b/src/features/recent-projects/main/composition/createRecentProjectsFeature.ts index f77986e8..f7109381 100644 --- a/src/features/recent-projects/main/composition/createRecentProjectsFeature.ts +++ b/src/features/recent-projects/main/composition/createRecentProjectsFeature.ts @@ -1,3 +1,8 @@ +import { + type DashboardRecentProjectsPayload, + normalizeDashboardRecentProjectsPayload, +} from '@features/recent-projects/contracts'; + import { ListDashboardRecentProjectsUseCase } from '../../core/application/use-cases/ListDashboardRecentProjectsUseCase'; import { DashboardRecentProjectsPresenter } from '../adapters/output/presenters/DashboardRecentProjectsPresenter'; import { ClaudeRecentProjectsSourceAdapter } from '../adapters/output/sources/ClaudeRecentProjectsSourceAdapter'; @@ -10,10 +15,6 @@ import { RecentProjectIdentityResolver } from '../infrastructure/identity/Recent import type { ClockPort } from '../../core/application/ports/ClockPort'; import type { LoggerPort } from '../../core/application/ports/LoggerPort'; -import { - normalizeDashboardRecentProjectsPayload, - type DashboardRecentProjectsPayload, -} from '@features/recent-projects/contracts'; import type { ServiceContext } from '@main/services'; export interface RecentProjectsFeatureFacade { diff --git a/src/features/recent-projects/renderer/utils/recentProjectsClientCache.ts b/src/features/recent-projects/renderer/utils/recentProjectsClientCache.ts index dc804d0d..40cdbf9e 100644 --- a/src/features/recent-projects/renderer/utils/recentProjectsClientCache.ts +++ b/src/features/recent-projects/renderer/utils/recentProjectsClientCache.ts @@ -1,9 +1,10 @@ -import type { - DashboardRecentProjectsPayloadLike, - DashboardRecentProjectsPayload, -} from '@features/recent-projects/contracts'; import { normalizeDashboardRecentProjectsPayload } from '@features/recent-projects/contracts'; +import type { + DashboardRecentProjectsPayload, + DashboardRecentProjectsPayloadLike, +} from '@features/recent-projects/contracts'; + const RECENT_PROJECTS_CLIENT_CACHE_TTL_MS = 15_000; const RECENT_PROJECTS_CLIENT_DEGRADED_CACHE_TTL_MS = 1_500; diff --git a/src/main/ipc/teams.ts b/src/main/ipc/teams.ts index b1ed5591..5cf50a96 100644 --- a/src/main/ipc/teams.ts +++ b/src/main/ipc/teams.ts @@ -180,8 +180,8 @@ import type { TeamSummary, TeamTask, TeamTaskStatus, - TeamViewSnapshot, TeamUpdateConfigRequest, + TeamViewSnapshot, ToolApprovalFileContent, ToolApprovalSettings, UpdateKanbanPatch, diff --git a/src/main/services/infrastructure/CliInstallerService.ts b/src/main/services/infrastructure/CliInstallerService.ts index 3f472c2e..c07328c1 100644 --- a/src/main/services/infrastructure/CliInstallerService.ts +++ b/src/main/services/infrastructure/CliInstallerService.ts @@ -50,8 +50,8 @@ import type { CliInstallationStatus, CliInstallerProgress, CliPlatform, - CliProviderModelAvailability, CliProviderId, + CliProviderModelAvailability, CliProviderStatus, } from '@shared/types'; import type { BrowserWindow } from 'electron'; @@ -596,7 +596,7 @@ export class CliInstallerService { private updateLatestProviderStatus(providerStatus: CliProviderStatus): void { if ( providerStatus.modelVerificationState !== 'verifying' && - !((providerStatus.modelAvailability?.length ?? 0) > 0) + (providerStatus.modelAvailability?.length ?? 0) <= 0 ) { this.latestProviderSignatures.set(providerStatus.providerId, null); } diff --git a/src/main/services/team/TeamDataService.ts b/src/main/services/team/TeamDataService.ts index a8f25489..e0ee5249 100644 --- a/src/main/services/team/TeamDataService.ts +++ b/src/main/services/team/TeamDataService.ts @@ -47,8 +47,8 @@ import { TeamInboxWriter } from './TeamInboxWriter'; import { TeamKanbanManager } from './TeamKanbanManager'; import { TeamMemberResolver } from './TeamMemberResolver'; import { TeamMemberRuntimeAdvisoryService } from './TeamMemberRuntimeAdvisoryService'; -import { TeamMessageFeedService } from './TeamMessageFeedService'; import { TeamMembersMetaStore } from './TeamMembersMetaStore'; +import { TeamMessageFeedService } from './TeamMessageFeedService'; import { TeamMetaStore } from './TeamMetaStore'; import { TeamSentMessagesStore } from './TeamSentMessagesStore'; import { TeamTaskCommentNotificationJournal } from './TeamTaskCommentNotificationJournal'; @@ -75,8 +75,8 @@ import type { TaskRef, TeamConfig, TeamCreateConfigRequest, - TeamMemberActivityMeta, TeamMember, + TeamMemberActivityMeta, TeamProcess, TeamSummary, TeamTask, diff --git a/src/main/services/team/TeamMemberResolver.ts b/src/main/services/team/TeamMemberResolver.ts index 3035d479..ec65957e 100644 --- a/src/main/services/team/TeamMemberResolver.ts +++ b/src/main/services/team/TeamMemberResolver.ts @@ -1,9 +1,9 @@ +import { getMemberColorByName } from '@shared/constants/memberColors'; import { createCliAutoSuffixNameGuard, createCliProvisionerNameGuard, } from '@shared/utils/teamMemberName'; import { getStableTeamOwnerId } from '@shared/utils/teamStableOwnerId'; -import { getMemberColorByName } from '@shared/constants/memberColors'; import type { TeamConfig, TeamMember, TeamMemberSnapshot, TeamTaskWithKanban } from '@shared/types'; diff --git a/src/main/services/team/TeamMessageFeedService.ts b/src/main/services/team/TeamMessageFeedService.ts index f961e867..a38075b0 100644 --- a/src/main/services/team/TeamMessageFeedService.ts +++ b/src/main/services/team/TeamMessageFeedService.ts @@ -1,7 +1,6 @@ -import { createHash } from 'crypto'; - -import { buildStandaloneSlashCommandMeta } from '@shared/utils/slashCommands'; import { classifyIdleNotificationText } from '@shared/utils/idleNotificationSemantics'; +import { buildStandaloneSlashCommandMeta } from '@shared/utils/slashCommands'; +import { createHash } from 'crypto'; import { getEffectiveInboxMessageId } from './inboxMessageIdentity'; diff --git a/src/main/services/team/TeamProvisioningService.ts b/src/main/services/team/TeamProvisioningService.ts index 483b91e9..04278d0d 100644 --- a/src/main/services/team/TeamProvisioningService.ts +++ b/src/main/services/team/TeamProvisioningService.ts @@ -33,6 +33,7 @@ import { import { getMemberColorByName } from '@shared/constants/memberColors'; import { DEFAULT_TOOL_APPROVAL_SETTINGS } from '@shared/types/team'; import { resolveLanguageName } from '@shared/utils/agentLanguage'; +import { getAnthropicDefaultTeamModel } from '@shared/utils/anthropicModelDefaults'; import { parseCliArgs } from '@shared/utils/cliArgsParser'; import { isInboxNoiseMessage, @@ -42,14 +43,13 @@ import { } from '@shared/utils/inboxNoise'; import { isLeadAgentType, isLeadMember } from '@shared/utils/leadDetection'; import { createLogger } from '@shared/utils/logger'; -import { getAnthropicDefaultTeamModel } from '@shared/utils/anthropicModelDefaults'; +import { isDefaultProviderModelSelection } from '@shared/utils/providerModelSelection'; import { formatTaskDisplayLabel } from '@shared/utils/taskIdentity'; import { parseAllTeammateMessages, type ParsedTeammateContent, } from '@shared/utils/teammateMessageParser'; import { createCliAutoSuffixNameGuard, parseNumericSuffixName } from '@shared/utils/teamMemberName'; -import { isDefaultProviderModelSelection } from '@shared/utils/providerModelSelection'; import { normalizeOptionalTeamProviderId } from '@shared/utils/teamProvider'; import { extractToolPreview, @@ -68,16 +68,16 @@ import { type GeminiRuntimeAuthState, resolveGeminiRuntimeAuth, } from '../runtime/geminiRuntimeAuth'; +import { buildProviderAwareCliEnv } from '../runtime/providerAwareCliEnv'; import { - buildProviderPreflightPingArgs, buildProviderModelProbeArgs, + buildProviderPreflightPingArgs, classifyProviderModelProbeFailure, getProviderModelProbeExpectedOutput, getProviderModelProbeTimeoutMs, isProviderModelProbeSuccessOutput, normalizeProviderModelProbeFailureReason, } from '../runtime/providerModelProbe'; -import { buildProviderAwareCliEnv } from '../runtime/providerAwareCliEnv'; import { resolveTeamProviderId } from '../runtime/providerRuntimeEnv'; import { buildActionModeProtocol } from './actionModeInstructions'; diff --git a/src/preload/index.ts b/src/preload/index.ts index f8a9af5e..b955ba91 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -265,7 +265,6 @@ import type { LeadContextUsageSnapshot, MemberFullStats, MemberLogSummary, - TeamMemberActivityMeta, MemberSpawnStatusesSnapshot, MessagesPage, NotificationTrigger, @@ -297,14 +296,15 @@ import type { TeamCreateResponse, TeamLaunchRequest, TeamLaunchResponse, + TeamMemberActivityMeta, TeamMessageNotificationData, - TeamViewSnapshot, TeamProvisioningPrepareResult, TeamProvisioningProgress, TeamSummary, TeamTask, TeamTaskStatus, TeamUpdateConfigRequest, + TeamViewSnapshot, ToolApprovalEvent, ToolApprovalFileContent, ToolApprovalSettings, diff --git a/src/renderer/api/httpClient.ts b/src/renderer/api/httpClient.ts index eb76362e..ddcec8fe 100644 --- a/src/renderer/api/httpClient.ts +++ b/src/renderer/api/httpClient.ts @@ -54,7 +54,6 @@ import type { SshLastConnection, SubagentDetail, TeamChangeEvent, - UpdateSchedulePatch, TeamClaudeLogsQuery, TeamClaudeLogsResponse, TeamCreateRequest, @@ -74,6 +73,7 @@ import type { TriggerTestResult, UpdateKanbanPatch, UpdaterAPI, + UpdateSchedulePatch, WaterfallData, WslClaudeRootCandidate, } from '@shared/types'; diff --git a/src/renderer/components/dashboard/CliStatusBanner.tsx b/src/renderer/components/dashboard/CliStatusBanner.tsx index be2ffe20..84d14d47 100644 --- a/src/renderer/components/dashboard/CliStatusBanner.tsx +++ b/src/renderer/components/dashboard/CliStatusBanner.tsx @@ -12,7 +12,6 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { api, isElectronMode } from '@renderer/api'; import { confirm } from '@renderer/components/common/ConfirmDialog'; import { ProviderBrandLogo } from '@renderer/components/common/ProviderBrandLogo'; -import { ProviderModelBadges } from '@renderer/components/runtime/ProviderModelBadges'; import { formatProviderStatusText, getProviderConnectionModeSummary, @@ -23,6 +22,7 @@ import { isConnectionManagedRuntimeProvider, shouldShowProviderConnectAction, } from '@renderer/components/runtime/providerConnectionUi'; +import { ProviderModelBadges } from '@renderer/components/runtime/ProviderModelBadges'; import { getProviderRuntimeBackendSummary } from '@renderer/components/runtime/ProviderRuntimeBackendSelector'; import { ProviderRuntimeSettingsDialog } from '@renderer/components/runtime/ProviderRuntimeSettingsDialog'; import { SettingsToggle } from '@renderer/components/settings/components'; diff --git a/src/renderer/components/extensions/plugins/PluginCard.tsx b/src/renderer/components/extensions/plugins/PluginCard.tsx index 24ca2d87..9ffe21d5 100644 --- a/src/renderer/components/extensions/plugins/PluginCard.tsx +++ b/src/renderer/components/extensions/plugins/PluginCard.tsx @@ -5,8 +5,8 @@ import { Badge } from '@renderer/components/ui/badge'; import { useStore } from '@renderer/store'; import { - getInstallationSummaryLabel, getCapabilityLabel, + getInstallationSummaryLabel, hasInstallationInScope, inferCapabilities, normalizeCategory, diff --git a/src/renderer/components/extensions/plugins/PluginDetailDialog.tsx b/src/renderer/components/extensions/plugins/PluginDetailDialog.tsx index 8d6e05ac..f0860b5e 100644 --- a/src/renderer/components/extensions/plugins/PluginDetailDialog.tsx +++ b/src/renderer/components/extensions/plugins/PluginDetailDialog.tsx @@ -25,8 +25,8 @@ import { } from '@renderer/components/ui/select'; import { useStore } from '@renderer/store'; import { - getInstallationSummaryLabel, getCapabilityLabel, + getInstallationSummaryLabel, hasInstallationInScope, inferCapabilities, normalizeCategory, diff --git a/src/renderer/components/runtime/ProviderModelBadges.tsx b/src/renderer/components/runtime/ProviderModelBadges.tsx index b4e0495f..ad1fcf0a 100644 --- a/src/renderer/components/runtime/ProviderModelBadges.tsx +++ b/src/renderer/components/runtime/ProviderModelBadges.tsx @@ -1,8 +1,8 @@ +import { cn } from '@renderer/lib/utils'; import { getTeamModelBadgeLabel, getVisibleTeamProviderModels, } from '@renderer/utils/teamModelCatalog'; -import { cn } from '@renderer/lib/utils'; import type { CliProviderId, @@ -43,7 +43,7 @@ function getAvailabilityChip(status: CliProviderModelAvailabilityStatus | null): } } -export function ProviderModelBadges({ +export const ProviderModelBadges = ({ providerId, models, modelAvailability, @@ -53,7 +53,7 @@ export function ProviderModelBadges({ readonly models: string[]; readonly modelAvailability?: CliProviderModelAvailability[]; readonly providerStatus?: Pick | null; -}): React.JSX.Element { +}): React.JSX.Element => { const visibleModels = getVisibleTeamProviderModels(providerId, models, providerStatus); return ( @@ -94,4 +94,4 @@ export function ProviderModelBadges({ })} ); -} +}; diff --git a/src/renderer/components/settings/sections/CliStatusSection.tsx b/src/renderer/components/settings/sections/CliStatusSection.tsx index 2524867b..104a2f15 100644 --- a/src/renderer/components/settings/sections/CliStatusSection.tsx +++ b/src/renderer/components/settings/sections/CliStatusSection.tsx @@ -10,7 +10,6 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { isElectronMode } from '@renderer/api'; import { confirm } from '@renderer/components/common/ConfirmDialog'; import { ProviderBrandLogo } from '@renderer/components/common/ProviderBrandLogo'; -import { ProviderModelBadges } from '@renderer/components/runtime/ProviderModelBadges'; import { formatProviderStatusText, getProviderConnectionModeSummary, @@ -21,6 +20,7 @@ import { isConnectionManagedRuntimeProvider, shouldShowProviderConnectAction, } from '@renderer/components/runtime/providerConnectionUi'; +import { ProviderModelBadges } from '@renderer/components/runtime/ProviderModelBadges'; import { getProviderRuntimeBackendSummary } from '@renderer/components/runtime/ProviderRuntimeBackendSelector'; import { ProviderRuntimeSettingsDialog } from '@renderer/components/runtime/ProviderRuntimeSettingsDialog'; import { SettingsToggle } from '@renderer/components/settings/components'; diff --git a/src/renderer/components/team/TeamListView.tsx b/src/renderer/components/team/TeamListView.tsx index f9d5e556..0b909a0c 100644 --- a/src/renderer/components/team/TeamListView.tsx +++ b/src/renderer/components/team/TeamListView.tsx @@ -56,10 +56,10 @@ import { import type { ActiveTeamRef, TeamCopyData } from './dialogs/CreateTeamDialog'; import type { TeamListFilterState } from './TeamListFilterPopover'; import type { - TeamMemberSnapshot, ResolvedTeamMember, TeamCreateRequest, TeamLaunchRequest, + TeamMemberSnapshot, TeamSummary, TeamSummaryMember, } from '@shared/types'; diff --git a/src/renderer/components/team/dialogs/CreateTeamDialog.tsx b/src/renderer/components/team/dialogs/CreateTeamDialog.tsx index 1a3037f6..f2b181e0 100644 --- a/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +++ b/src/renderer/components/team/dialogs/CreateTeamDialog.tsx @@ -43,6 +43,7 @@ import { import { normalizePath } from '@renderer/utils/pathNormalize'; import { getTeamModelSelectionError, + normalizeExplicitTeamModelForUi, normalizeTeamModelForUi, } from '@renderer/utils/teamModelAvailability'; import { getTeamProviderLabel as getCatalogTeamProviderLabel } from '@renderer/utils/teamModelCatalog'; @@ -53,22 +54,22 @@ import { AlertTriangle, CheckCircle2, Info, Loader2, X } from 'lucide-react'; import { AdvancedCliSection } from './AdvancedCliSection'; import { OptionalSettingsSection } from './OptionalSettingsSection'; import { ProjectPathSelector } from './ProjectPathSelector'; +import { + getProviderPrepareCachedSnapshot, + type ProviderPrepareDiagnosticsModelResult, + runProviderPrepareDiagnostics, +} from './providerPrepareDiagnostics'; +import { getProvisioningModelIssue } from './provisioningModelIssues'; import { failIncompleteProviderChecks, - getProvisioningFailureHint, getPrimaryProvisioningFailureDetail, + getProvisioningFailureHint, getProvisioningProviderBackendSummary, type ProvisioningProviderCheck, ProvisioningProviderStatusList, shouldHideProvisioningProviderStatusList, updateProviderCheck, } from './ProvisioningProviderStatusList'; -import { getProvisioningModelIssue } from './provisioningModelIssues'; -import { - getProviderPrepareCachedSnapshot, - runProviderPrepareDiagnostics, - type ProviderPrepareDiagnosticsModelResult, -} from './providerPrepareDiagnostics'; import { SkipPermissionsCheckbox } from './SkipPermissionsCheckbox'; import { computeEffectiveTeamModel } from './TeamModelSelector'; import { getNextSuggestedTeamName } from './teamNameSets'; @@ -108,7 +109,7 @@ function getStoredTeamModel(providerId: TeamProviderId): string { if (stored === null) { return providerId === 'anthropic' ? 'opus' : ''; } - return normalizeTeamModelForUi(providerId, stored === '__default__' ? '' : stored); + return normalizeExplicitTeamModelForUi(providerId, stored === '__default__' ? '' : stored); } function isEphemeralRenderedProjectPath(projectPath: string | null | undefined): boolean { diff --git a/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx b/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx index 2b3a6f4e..565ff163 100644 --- a/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx +++ b/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx @@ -48,6 +48,7 @@ import { normalizePath } from '@renderer/utils/pathNormalize'; import { nameColorSet } from '@renderer/utils/projectColor'; import { getTeamModelSelectionError, + normalizeExplicitTeamModelForUi, normalizeTeamModelForUi, } from '@renderer/utils/teamModelAvailability'; import { getTeamProviderLabel as getCatalogTeamProviderLabel } from '@renderer/utils/teamModelCatalog'; @@ -72,22 +73,22 @@ import { EffortLevelSelector } from './EffortLevelSelector'; import { resolveLaunchDialogPrefill } from './launchDialogPrefill'; import { OptionalSettingsSection } from './OptionalSettingsSection'; import { ProjectPathSelector } from './ProjectPathSelector'; +import { + getProviderPrepareCachedSnapshot, + type ProviderPrepareDiagnosticsModelResult, + runProviderPrepareDiagnostics, +} from './providerPrepareDiagnostics'; +import { getProvisioningModelIssue } from './provisioningModelIssues'; import { failIncompleteProviderChecks, - getProvisioningFailureHint, getPrimaryProvisioningFailureDetail, + getProvisioningFailureHint, getProvisioningProviderBackendSummary, type ProvisioningProviderCheck, ProvisioningProviderStatusList, shouldHideProvisioningProviderStatusList, updateProviderCheck, } from './ProvisioningProviderStatusList'; -import { getProvisioningModelIssue } from './provisioningModelIssues'; -import { - getProviderPrepareCachedSnapshot, - runProviderPrepareDiagnostics, - type ProviderPrepareDiagnosticsModelResult, -} from './providerPrepareDiagnostics'; import { computeEffectiveTeamModel, formatTeamModelSummary, @@ -195,7 +196,7 @@ function getStoredTeamModel(providerId: TeamProviderId): string { if (stored === null) { return providerId === 'anthropic' ? 'opus' : ''; } - return normalizeTeamModelForUi(providerId, stored === '__default__' ? '' : stored); + return normalizeExplicitTeamModelForUi(providerId, stored === '__default__' ? '' : stored); } function getProviderLabel(providerId: TeamProviderId): string { diff --git a/src/renderer/components/team/dialogs/TeamModelSelector.tsx b/src/renderer/components/team/dialogs/TeamModelSelector.tsx index 0e593d34..08b44e1e 100644 --- a/src/renderer/components/team/dialogs/TeamModelSelector.tsx +++ b/src/renderer/components/team/dialogs/TeamModelSelector.tsx @@ -11,7 +11,6 @@ import { } from '@renderer/components/ui/tooltip'; import { cn } from '@renderer/lib/utils'; import { useStore } from '@renderer/store'; -import { getAnthropicDefaultTeamModel } from '@shared/utils/anthropicModelDefaults'; import { GEMINI_UI_DISABLED_BADGE_LABEL, GEMINI_UI_DISABLED_REASON, @@ -30,6 +29,7 @@ import { getTeamProviderLabel as getCatalogTeamProviderLabel, } from '@renderer/utils/teamModelCatalog'; import { extractProviderScopedBaseModel } from '@renderer/utils/teamModelContext'; +import { getAnthropicDefaultTeamModel } from '@shared/utils/anthropicModelDefaults'; import { AlertTriangle, Info } from 'lucide-react'; export { getProviderScopedTeamModelLabel } from '@renderer/utils/teamModelCatalog'; diff --git a/src/renderer/components/team/dialogs/launchDialogPrefill.ts b/src/renderer/components/team/dialogs/launchDialogPrefill.ts index 5651a207..242bfb42 100644 --- a/src/renderer/components/team/dialogs/launchDialogPrefill.ts +++ b/src/renderer/components/team/dialogs/launchDialogPrefill.ts @@ -1,5 +1,5 @@ import { normalizeCreateLaunchProviderForUi } from '@renderer/utils/geminiUiFreeze'; -import { normalizeTeamModelForUi } from '@renderer/utils/teamModelAvailability'; +import { normalizeExplicitTeamModelForUi } from '@renderer/utils/teamModelAvailability'; import { extractProviderScopedBaseModel } from '@renderer/utils/teamModelContext'; import { isLeadMember } from '@shared/utils/leadDetection'; import { normalizeOptionalTeamProviderId } from '@shared/utils/teamProvider'; @@ -102,7 +102,7 @@ export function resolveLaunchDialogPrefill({ return { providerId, model: matchingModel - ? normalizeTeamModelForUi(providerId, matchingModel) + ? normalizeExplicitTeamModelForUi(providerId, matchingModel) : getStoredModel(providerId), effort, limitContext, diff --git a/src/renderer/components/team/dialogs/providerPrepareDiagnostics.ts b/src/renderer/components/team/dialogs/providerPrepareDiagnostics.ts index 91826258..cf1bb17d 100644 --- a/src/renderer/components/team/dialogs/providerPrepareDiagnostics.ts +++ b/src/renderer/components/team/dialogs/providerPrepareDiagnostics.ts @@ -5,15 +5,13 @@ import type { TeamProviderId, TeamProvisioningPrepareResult } from '@shared/type export type ProviderPrepareCheckStatus = 'ready' | 'notes' | 'failed'; -interface PrepareProvisioningFn { - ( - cwd?: string, - providerId?: TeamProviderId, - providerIds?: TeamProviderId[], - selectedModels?: string[], - limitContext?: boolean - ): Promise; -} +type PrepareProvisioningFn = ( + cwd?: string, + providerId?: TeamProviderId, + providerIds?: TeamProviderId[], + selectedModels?: string[], + limitContext?: boolean +) => Promise; interface ProviderPrepareDiagnosticsProgress { details: string[]; @@ -156,15 +154,15 @@ function normalizeModelReason(rawReason: string | null | undefined): string | nu return 'Model verification timed out'; } - const detailMatch = trimmed.match(/"detail":"((?:\\"|[^"])*)"/i); + const detailMatch = /"detail":"((?:\\"|[^"])*)"/i.exec(trimmed); if (detailMatch?.[1]) { return normalizeModelReason(detailMatch[1].replace(/\\"/g, '"').trim()); } - const messageMatch = trimmed.match(/"message":"((?:\\"|[^"])*)"/i); + const messageMatch = /"message":"((?:\\"|[^"])*)"/i.exec(trimmed); if (messageMatch?.[1]) { const decodedMessage = messageMatch[1].replace(/\\"/g, '"'); - const nestedDetailMatch = decodedMessage.match(/"detail":"([^"]+)"/i); + const nestedDetailMatch = /"detail":"([^"]+)"/i.exec(decodedMessage); if (nestedDetailMatch?.[1]) { return normalizeModelReason(nestedDetailMatch[1].trim()); } diff --git a/src/renderer/components/team/members/MemberDraftRow.tsx b/src/renderer/components/team/members/MemberDraftRow.tsx index 79deb794..a419f6b0 100644 --- a/src/renderer/components/team/members/MemberDraftRow.tsx +++ b/src/renderer/components/team/members/MemberDraftRow.tsx @@ -16,8 +16,8 @@ import { getTeamColorSet } from '@renderer/constants/teamColors'; import { useDraftPersistence } from '@renderer/hooks/useDraftPersistence'; import { useFileListCacheWarmer } from '@renderer/hooks/useFileListCacheWarmer'; import { useTheme } from '@renderer/hooks/useTheme'; -import { reconcileChips, removeChipTokenFromText } from '@renderer/utils/chipUtils'; import { cn } from '@renderer/lib/utils'; +import { reconcileChips, removeChipTokenFromText } from '@renderer/utils/chipUtils'; import { getMemberColorByName } from '@shared/constants/memberColors'; import { AlertTriangle, ChevronDown, ChevronRight, Info, RotateCcw, Trash2 } from 'lucide-react'; diff --git a/src/renderer/components/team/members/MemberList.tsx b/src/renderer/components/team/members/MemberList.tsx index 58db6a84..341a19c1 100644 --- a/src/renderer/components/team/members/MemberList.tsx +++ b/src/renderer/components/team/members/MemberList.tsx @@ -1,7 +1,7 @@ import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { resolveMemberRuntimeSummary } from '@renderer/utils/memberRuntimeSummary'; import { buildMemberColorMap } from '@renderer/utils/memberHelpers'; +import { resolveMemberRuntimeSummary } from '@renderer/utils/memberRuntimeSummary'; import { isLeadMember } from '@shared/utils/leadDetection'; import { MemberCard } from './MemberCard'; diff --git a/src/renderer/components/team/members/membersEditorUtils.ts b/src/renderer/components/team/members/membersEditorUtils.ts index 9fafc7db..b69843ad 100644 --- a/src/renderer/components/team/members/membersEditorUtils.ts +++ b/src/renderer/components/team/members/membersEditorUtils.ts @@ -2,7 +2,7 @@ import { CUSTOM_ROLE, NO_ROLE, PRESET_ROLES } from '@renderer/constants/teamRole import { serializeChipsWithText } from '@renderer/types/inlineChip'; import { normalizeCreateLaunchProviderForUi } from '@renderer/utils/geminiUiFreeze'; import { buildMemberColorMap } from '@renderer/utils/memberHelpers'; -import { normalizeTeamModelForUi } from '@renderer/utils/teamModelAvailability'; +import { normalizeExplicitTeamModelForUi } from '@renderer/utils/teamModelAvailability'; import { isLeadMember } from '@shared/utils/leadDetection'; import { normalizeOptionalTeamProviderId } from '@shared/utils/teamProvider'; @@ -39,7 +39,7 @@ export function createMemberDraft(initial?: Partial): MemberDraft { customRole: initial?.customRole ?? '', workflow: initial?.workflow, providerId, - model: normalizeTeamModelForUi(providerId, initial?.model ?? ''), + model: normalizeExplicitTeamModelForUi(providerId, initial?.model ?? ''), effort: initial?.effort, removedAt: initial?.removedAt, }; @@ -218,7 +218,7 @@ export function buildMembersFromDrafts(members: MemberDraft[]): TeamProvisioning } const model = member.model?.trim(); if (model) { - result.model = normalizeTeamModelForUi(providerId, model); + result.model = normalizeExplicitTeamModelForUi(providerId, model); } const effort = normalizeDraftEffort(member.effort); if (effort) { diff --git a/src/renderer/components/team/messages/MessagesPanel.tsx b/src/renderer/components/team/messages/MessagesPanel.tsx index 791a8513..4a7d64ec 100644 --- a/src/renderer/components/team/messages/MessagesPanel.tsx +++ b/src/renderer/components/team/messages/MessagesPanel.tsx @@ -12,7 +12,6 @@ import { selectTeamMessages } from '@renderer/store/slices/teamSlice'; import { filterTeamMessages } from '@renderer/utils/teamMessageFiltering'; import { toMessageKey } from '@renderer/utils/teamMessageKey'; import { shouldExcludeInboxTextFromReplyCandidates } from '@shared/utils/idleNotificationSemantics'; -import { createLogger } from '@shared/utils/logger'; import { CheckCheck, ChevronsDownUp, @@ -52,9 +51,6 @@ interface TimeWindow { end: number; } -const logger = createLogger('Component:MessagesPanel'); -const MESSAGES_PANEL_FILTER_WARN_MS = 8; -const MESSAGES_PANEL_EXPANDED_ITEM_WARN_MS = 6; const BOTTOM_SHEET_HEADER_HEIGHT = 40; const BOTTOM_SHEET_COLLAPSED_SNAP_INDEX = 1; const BOTTOM_SHEET_COMPOSER_SNAP_INDEX = 2; @@ -272,41 +268,21 @@ export const MessagesPanel = memo(function MessagesPanel({ }, [position, mountPoint]); const filteredMessages = useMemo(() => { - const startedAt = performance.now(); - const result = filterTeamMessages(effectiveMessages, { + return filterTeamMessages(effectiveMessages, { timeWindow, filter: messagesFilter, searchQuery: messagesSearchQuery, }); - const ms = performance.now() - startedAt; - if (ms >= MESSAGES_PANEL_FILTER_WARN_MS) { - logger.warn( - `[perf] filter team=${teamName} stage=messages ms=${ms.toFixed(1)} input=${effectiveMessages.length} output=${result.length} searchLen=${messagesSearchQuery.trim().length} noise=${ - messagesFilter.showNoise ? 'on' : 'off' - }` - ); - } - return result; - }, [effectiveMessages, messagesFilter, messagesSearchQuery, teamName, timeWindow]); + }, [effectiveMessages, messagesFilter, messagesSearchQuery, timeWindow]); const activityTimelineMessages = useMemo(() => { - const startedAt = performance.now(); - const result = filterTeamMessages(effectiveMessages, { + return filterTeamMessages(effectiveMessages, { includePassiveIdlePeerSummariesWhenNoiseHidden: true, timeWindow, filter: messagesFilter, searchQuery: messagesSearchQuery, }); - const ms = performance.now() - startedAt; - if (ms >= MESSAGES_PANEL_FILTER_WARN_MS) { - logger.warn( - `[perf] filter team=${teamName} stage=timeline ms=${ms.toFixed(1)} input=${effectiveMessages.length} output=${result.length} searchLen=${messagesSearchQuery.trim().length} noise=${ - messagesFilter.showNoise ? 'on' : 'off' - }` - ); - } - return result; - }, [effectiveMessages, messagesFilter, messagesSearchQuery, teamName, timeWindow]); + }, [effectiveMessages, messagesFilter, messagesSearchQuery, timeWindow]); const replyCandidateMessages = useMemo( () => @@ -320,33 +296,21 @@ export const MessagesPanel = memo(function MessagesPanel({ // Resolve the expanded item from filtered messages const expandedItem = useMemo(() => { - const startedAt = performance.now(); - if (!expandedItemKey) return null; + if (!expandedItemKey) { + return null; + } if (!expandedItemKey.startsWith('thoughts-')) { const msg = activityTimelineMessages.find((m) => toMessageKey(m) === expandedItemKey); - const result: TimelineItem | null = msg ? { type: 'message', message: msg } : null; - const ms = performance.now() - startedAt; - if (ms >= MESSAGES_PANEL_EXPANDED_ITEM_WARN_MS) { - logger.warn( - `[perf] expandedItem team=${teamName} ms=${ms.toFixed(1)} mode=message timelineMessages=${activityTimelineMessages.length}` - ); - } - return result; + return msg ? { type: 'message', message: msg } : null; } const allItems = groupTimelineItems(activityTimelineMessages); - const result = + return ( allItems.find( (item) => item.type === 'lead-thoughts' && getThoughtGroupKey(item.group) === expandedItemKey - ) ?? null; - const ms = performance.now() - startedAt; - if (ms >= MESSAGES_PANEL_EXPANDED_ITEM_WARN_MS) { - logger.warn( - `[perf] expandedItem team=${teamName} ms=${ms.toFixed(1)} mode=thoughts timelineMessages=${activityTimelineMessages.length} groups=${allItems.length}` - ); - } - return result; - }, [expandedItemKey, activityTimelineMessages, teamName]); + ) ?? null + ); + }, [expandedItemKey, activityTimelineMessages]); // Auto-clear stale expanded key useEffect(() => { diff --git a/src/renderer/store/slices/extensionsSlice.ts b/src/renderer/store/slices/extensionsSlice.ts index 7932eeeb..bf9e28ed 100644 --- a/src/renderer/store/slices/extensionsSlice.ts +++ b/src/renderer/store/slices/extensionsSlice.ts @@ -127,7 +127,7 @@ export interface ExtensionsSlice { // Slice Creator // ============================================================================= -let pluginFetchInFlight: { key: string; promise: Promise } | null = null; +let pluginFetchInFlight: { key: string; promise: Promise; token: symbol } | null = null; let pluginCatalogRequestSeq = 0; const pluginSuccessResetTimers = new Map>(); let mcpDiagnosticsInFlight: Promise | null = null; @@ -286,6 +286,7 @@ export const createExtensionsSlice: StateCreator { @@ -344,13 +345,13 @@ export const createExtensionsSlice: StateCreator[number], 'name' | 'agentId' | 'removedAt' >; -type TeamGraphLayoutSessionState = { +interface TeamGraphLayoutSessionState { mode: 'default' | 'manual'; signature: string | null; -}; +} export function isTeamDataRefreshPending(teamName: string): boolean { return ( @@ -3202,8 +3202,7 @@ export const createTeamSlice: StateCreator = (set, const existingOlderRequest = inFlightTeamMessagesOlderRequests.get(teamName); if (existingOlderRequest) { - let queuedRequest: Promise; - queuedRequest = existingOlderRequest + const queuedRequest: Promise = existingOlderRequest .then(() => { if (queuedTeamMessagesHeadRefreshesAfterOlder.get(teamName) === queuedRequest) { queuedTeamMessagesHeadRefreshesAfterOlder.delete(teamName); diff --git a/src/renderer/utils/memberRuntimeSummary.ts b/src/renderer/utils/memberRuntimeSummary.ts index 937a4f0f..b8246167 100644 --- a/src/renderer/utils/memberRuntimeSummary.ts +++ b/src/renderer/utils/memberRuntimeSummary.ts @@ -1,8 +1,8 @@ import { formatTeamModelSummary } from '@renderer/components/team/dialogs/TeamModelSelector'; +import { inferTeamProviderIdFromModel } from '@shared/utils/teamProvider'; import type { TeamLaunchParams } from '@renderer/store/slices/teamSlice'; import type { MemberSpawnStatusEntry, ResolvedTeamMember, TeamProviderId } from '@shared/types'; -import { inferTeamProviderIdFromModel } from '@shared/utils/teamProvider'; function isMemberLaunchPending(spawnEntry: MemberSpawnStatusEntry | undefined): boolean { if (!spawnEntry) { diff --git a/src/renderer/utils/teamModelAvailability.ts b/src/renderer/utils/teamModelAvailability.ts index f42d2275..2e52843d 100644 --- a/src/renderer/utils/teamModelAvailability.ts +++ b/src/renderer/utils/teamModelAvailability.ts @@ -1,3 +1,23 @@ +import { + getProviderScopedTeamModelLabel, + getRuntimeAwareTeamModelUiDisabledReason, + getTeamProviderLabel, + getTeamProviderModelOptions, + getVisibleTeamProviderModels, + GPT_5_1_CODEX_MAX_CHATGPT_UI_DISABLED_REASON, + GPT_5_1_CODEX_MINI_UI_DISABLED_MODEL, + GPT_5_1_CODEX_MINI_UI_DISABLED_REASON, + GPT_5_2_CODEX_UI_DISABLED_MODEL, + GPT_5_2_CODEX_UI_DISABLED_REASON, + GPT_5_3_CODEX_SPARK_UI_DISABLED_MODEL, + GPT_5_3_CODEX_SPARK_UI_DISABLED_REASON, + normalizeTeamModelForUi as normalizeCatalogTeamModelForUi, + sortTeamProviderModels, + TEAM_MODEL_UI_DISABLED_BADGE_LABEL, + type TeamProviderModelOption, +} from './teamModelCatalog'; +import { extractProviderScopedBaseModel } from './teamModelContext'; + import type { CliProviderId, CliProviderModelAvailability, @@ -6,29 +26,10 @@ import type { TeamProviderId, } from '@shared/types'; -import { - getProviderScopedTeamModelLabel, - getRuntimeAwareTeamModelUiDisabledReason, - getTeamProviderLabel, - getTeamProviderModelOptions, - sortTeamProviderModels, - getVisibleTeamProviderModels, - normalizeTeamModelForUi as normalizeCatalogTeamModelForUi, - GPT_5_1_CODEX_MINI_UI_DISABLED_MODEL, - GPT_5_1_CODEX_MINI_UI_DISABLED_REASON, - GPT_5_1_CODEX_MAX_CHATGPT_UI_DISABLED_REASON, - GPT_5_2_CODEX_UI_DISABLED_MODEL, - GPT_5_2_CODEX_UI_DISABLED_REASON, - GPT_5_3_CODEX_SPARK_UI_DISABLED_MODEL, - GPT_5_3_CODEX_SPARK_UI_DISABLED_REASON, - TEAM_MODEL_UI_DISABLED_BADGE_LABEL, - type TeamProviderModelOption, -} from './teamModelCatalog'; - export { + GPT_5_1_CODEX_MAX_CHATGPT_UI_DISABLED_REASON, GPT_5_1_CODEX_MINI_UI_DISABLED_MODEL, GPT_5_1_CODEX_MINI_UI_DISABLED_REASON, - GPT_5_1_CODEX_MAX_CHATGPT_UI_DISABLED_REASON, GPT_5_2_CODEX_UI_DISABLED_MODEL, GPT_5_2_CODEX_UI_DISABLED_REASON, GPT_5_3_CODEX_SPARK_UI_DISABLED_MODEL, @@ -236,6 +237,14 @@ export function isTeamModelAvailableForUi( return getRuntimeModelAvailability(providerId, trimmed, providerStatus) === 'available'; } +export function normalizeExplicitTeamModelForUi( + providerId: SupportedProviderId | undefined, + model: string | undefined +): string { + const normalized = extractProviderScopedBaseModel(model, providerId) ?? ''; + return normalizeCatalogTeamModelForUi(providerId, normalized).trim(); +} + export function normalizeTeamModelForUi( providerId: SupportedProviderId | undefined, model: string | undefined, diff --git a/src/renderer/utils/teamModelCatalog.ts b/src/renderer/utils/teamModelCatalog.ts index 47248f19..e53172d8 100644 --- a/src/renderer/utils/teamModelCatalog.ts +++ b/src/renderer/utils/teamModelCatalog.ts @@ -1,4 +1,3 @@ -import type { CliProviderId, CliProviderStatus, TeamProviderId } from '@shared/types'; import { filterVisibleProviderRuntimeModels, GPT_5_1_CODEX_MINI_UI_DISABLED_MODEL, @@ -6,6 +5,8 @@ import { GPT_5_3_CODEX_SPARK_UI_DISABLED_MODEL, } from '@shared/utils/providerModelVisibility'; +import type { CliProviderId, CliProviderStatus, TeamProviderId } from '@shared/types'; + export { GPT_5_1_CODEX_MINI_UI_DISABLED_MODEL, GPT_5_2_CODEX_UI_DISABLED_MODEL, diff --git a/src/shared/types/api.ts b/src/shared/types/api.ts index 47f2870f..aa108dbb 100644 --- a/src/shared/types/api.ts +++ b/src/shared/types/api.ts @@ -53,7 +53,6 @@ import type { KanbanColumnId, LeadActivitySnapshot, LeadContextUsageSnapshot, - TeamMemberActivityMeta, MemberFullStats, MemberLogSummary, MemberSpawnStatusesSnapshot, @@ -74,14 +73,15 @@ import type { TeamCreateResponse, TeamLaunchRequest, TeamLaunchResponse, + TeamMemberActivityMeta, TeamMessageNotificationData, - TeamViewSnapshot, TeamProvisioningPrepareResult, TeamProvisioningProgress, TeamSummary, TeamTask, TeamTaskStatus, TeamUpdateConfigRequest, + TeamViewSnapshot, ToolApprovalEvent, ToolApprovalFileContent, ToolApprovalSettings, diff --git a/src/shared/utils/extensionNormalizers.ts b/src/shared/utils/extensionNormalizers.ts index 7684925a..3391c064 100644 --- a/src/shared/utils/extensionNormalizers.ts +++ b/src/shared/utils/extensionNormalizers.ts @@ -4,8 +4,8 @@ import type { CliInstallationStatus, - InstallScope, InstalledPluginEntry, + InstallScope, PluginCapability, PluginCatalogItem, } from '@shared/types'; diff --git a/test/main/services/infrastructure/CliInstallerService.test.ts b/test/main/services/infrastructure/CliInstallerService.test.ts index ffad35a7..b48ddc71 100644 --- a/test/main/services/infrastructure/CliInstallerService.test.ts +++ b/test/main/services/infrastructure/CliInstallerService.test.ts @@ -288,11 +288,11 @@ describe('CliInstallerService', () => { expect(status.providers.find((provider) => provider.providerId === 'codex')?.modelAvailability).toEqual([]); const verifiedProvider = await service.verifyProviderModels('codex'); - expect(verifiedProvider?.modelAvailability).toEqual( - expect.arrayContaining([ - expect.objectContaining({ modelId: 'gpt-5.4', status: 'checking' }), - expect.objectContaining({ modelId: 'gpt-5.2-codex', status: 'checking' }), - ]) + expect(verifiedProvider?.modelAvailability).toEqual([ + expect.objectContaining({ modelId: 'gpt-5.4', status: 'checking' }), + ]); + expect(verifiedProvider?.modelAvailability).not.toEqual( + expect.arrayContaining([expect.objectContaining({ modelId: 'gpt-5.2-codex' })]) ); await vi.waitFor(() => { @@ -302,13 +302,15 @@ describe('CliInstallerService', () => { expect(latestCodexProvider?.modelAvailability).toEqual([ expect.objectContaining({ modelId: 'gpt-5.4', status: 'available' }), - expect.objectContaining({ - modelId: 'gpt-5.2-codex', - status: 'unavailable', - }), ]); }); + expect(execCli).not.toHaveBeenCalledWith( + '/usr/local/bin/claude', + expect.arrayContaining(['--model', 'gpt-5.2-codex']), + expect.anything() + ); + const statusEvents = mockWindow.webContents.send.mock.calls .filter((call: unknown[]) => call[0] === 'cliInstaller:progress') .map((call: unknown[]) => call[1] as { type?: string; status?: { providers?: unknown[] } }) @@ -325,12 +327,29 @@ describe('CliInstallerService', () => { 'modelAvailability' in provider && (provider as { providerId?: string }).providerId === 'codex' && Array.isArray((provider as { modelAvailability?: unknown[] }).modelAvailability) && - (provider as { modelAvailability: Array<{ status?: string }> }).modelAvailability.some( - (item) => item.status === 'unavailable' - ) + (provider as { modelAvailability: Array<{ modelId?: string; status?: string }> }) + .modelAvailability.some( + (item) => item.modelId === 'gpt-5.4' && item.status === 'available' + ) ) ) ).toBe(true); + expect( + statusEvents.some((event) => + event.status?.providers?.some( + (provider) => + typeof provider === 'object' && + provider !== null && + 'providerId' in provider && + 'modelAvailability' in provider && + (provider as { providerId?: string }).providerId === 'codex' && + Array.isArray((provider as { modelAvailability?: unknown[] }).modelAvailability) && + (provider as { modelAvailability: Array<{ modelId?: string }> }).modelAvailability.some( + (item) => item.modelId === 'gpt-5.2-codex' + ) + ) + ) + ).toBe(false); }); }); diff --git a/test/main/services/runtime/CliProviderModelAvailabilityService.test.ts b/test/main/services/runtime/CliProviderModelAvailabilityService.test.ts index a498882d..eb7d6192 100644 --- a/test/main/services/runtime/CliProviderModelAvailabilityService.test.ts +++ b/test/main/services/runtime/CliProviderModelAvailabilityService.test.ts @@ -72,18 +72,20 @@ describe('CliProviderModelAvailabilityService', () => { expect(execCliMock).toHaveBeenCalledTimes(2); }); - it('marks unsupported models as unavailable with the runtime reason', async () => { + it('marks visible unsupported models as unavailable with the runtime reason', async () => { buildProviderAwareCliEnvMock.mockResolvedValue({ env: { HOME: '/Users/tester' }, connectionIssues: {}, }); execCliMock.mockRejectedValue( - new Error("The 'gpt-5.2-codex' model is not supported when using Codex with a ChatGPT account.") + new Error( + "The 'gpt-5.1-codex-max' model is not supported when using Codex with a ChatGPT account." + ) ); const onUpdate = vi.fn(); const service = new CliProviderModelAvailabilityService(onUpdate); - service.getSnapshot(createContext(['gpt-5.2-codex'])); + service.getSnapshot(createContext(['gpt-5.1-codex-max'])); await vi.waitFor(() => { expect(onUpdate).toHaveBeenCalledWith( @@ -92,7 +94,7 @@ describe('CliProviderModelAvailabilityService', () => { expect.objectContaining({ modelAvailability: [ expect.objectContaining({ - modelId: 'gpt-5.2-codex', + modelId: 'gpt-5.1-codex-max', status: 'unavailable', reason: 'Not available with Codex ChatGPT subscription', }), diff --git a/test/main/services/team/TeammateToolTracker.test.ts b/test/main/services/team/TeammateToolTracker.test.ts index c6ea4f49..a41e0ccc 100644 --- a/test/main/services/team/TeammateToolTracker.test.ts +++ b/test/main/services/team/TeammateToolTracker.test.ts @@ -49,6 +49,13 @@ function createDeferred(): Deferred { return { promise, resolve, reject }; } +function createLogsFinderMock(listAttributedMemberFiles: ReturnType) { + return { + listAttributedMemberFiles, + listAttributedSubagentFiles: listAttributedMemberFiles, + } as never; +} + describe('TeammateToolTracker', () => { const tempDirs: string[] = []; @@ -90,7 +97,7 @@ describe('TeammateToolTracker', () => { const events: TeamChangeEvent[] = []; const tracker = new TeammateToolTracker( - { listAttributedSubagentFiles } as never, + createLogsFinderMock(listAttributedSubagentFiles), { enableTracking, disableTracking } as never, (event) => events.push(event) ); @@ -136,7 +143,7 @@ describe('TeammateToolTracker', () => { ]); const events: TeamChangeEvent[] = []; const tracker = new TeammateToolTracker( - { listAttributedSubagentFiles } as never, + createLogsFinderMock(listAttributedSubagentFiles), { enableTracking: vi.fn(async () => ({ projectFingerprint: null, logSourceGeneration: null })), disableTracking: vi.fn(async () => ({ projectFingerprint: null, logSourceGeneration: null })) } as never, (event) => events.push(event) ); @@ -193,7 +200,7 @@ describe('TeammateToolTracker', () => { const listAttributedSubagentFiles = vi.fn(async () => [...attributedFiles]); const events: TeamChangeEvent[] = []; const tracker = new TeammateToolTracker( - { listAttributedSubagentFiles } as never, + createLogsFinderMock(listAttributedSubagentFiles), { enableTracking: vi.fn(async () => ({ projectFingerprint: null, logSourceGeneration: null })), disableTracking: vi.fn(async () => ({ projectFingerprint: null, logSourceGeneration: null })) } as never, (event) => events.push(event) ); @@ -242,7 +249,7 @@ describe('TeammateToolTracker', () => { ]); const events: TeamChangeEvent[] = []; const tracker = new TeammateToolTracker( - { listAttributedSubagentFiles } as never, + createLogsFinderMock(listAttributedSubagentFiles), { enableTracking: vi.fn(async () => ({ projectFingerprint: null, logSourceGeneration: null })), disableTracking: vi.fn(async () => ({ projectFingerprint: null, logSourceGeneration: null })) } as never, (event) => events.push(event) ); @@ -299,7 +306,7 @@ describe('TeammateToolTracker', () => { ]); const events: TeamChangeEvent[] = []; const tracker = new TeammateToolTracker( - { listAttributedSubagentFiles } as never, + createLogsFinderMock(listAttributedSubagentFiles), { enableTracking: vi.fn(async () => ({ projectFingerprint: null, logSourceGeneration: null })), disableTracking: vi.fn(async () => ({ projectFingerprint: null, logSourceGeneration: null })) } as never, (event) => events.push(event) ); @@ -353,7 +360,7 @@ describe('TeammateToolTracker', () => { const disableTracking = vi.fn(async () => ({ projectFingerprint: null, logSourceGeneration: null })); const events: TeamChangeEvent[] = []; const tracker = new TeammateToolTracker( - { listAttributedSubagentFiles } as never, + createLogsFinderMock(listAttributedSubagentFiles), { enableTracking, disableTracking } as never, (event) => events.push(event) ); diff --git a/test/renderer/components/team/members/MemberCard.test.ts b/test/renderer/components/team/members/MemberCard.test.ts index ff2426eb..6fa3cd14 100644 --- a/test/renderer/components/team/members/MemberCard.test.ts +++ b/test/renderer/components/team/members/MemberCard.test.ts @@ -61,7 +61,7 @@ describe('MemberCard starting-state visuals', () => { document.body.innerHTML = ''; }); - it('shows starting skeleton treatment even after provisioning is no longer active', async () => { + it('shows runtime summary while keeping the starting treatment after provisioning stops', async () => { vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true); const host = document.createElement('div'); document.body.appendChild(host); @@ -84,8 +84,9 @@ describe('MemberCard starting-state visuals', () => { }); expect(host.textContent).toContain('starting'); + expect(host.textContent).toContain('Anthropic · haiku · Medium'); expect(host.querySelector('.member-waiting-shimmer')).not.toBeNull(); - expect(host.querySelectorAll('.skeleton-shimmer').length).toBeGreaterThan(0); + expect(host.querySelectorAll('.skeleton-shimmer').length).toBe(0); await act(async () => { root.unmount(); @@ -173,7 +174,7 @@ describe('MemberCard starting-state visuals', () => { }); }); - it('keeps the starting skeleton visible while a runtime is alive but still joining', async () => { + it('keeps the starting treatment and runtime summary visible while a runtime is still joining', async () => { vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true); const host = document.createElement('div'); document.body.appendChild(host); @@ -197,9 +198,10 @@ describe('MemberCard starting-state visuals', () => { }); expect(host.textContent).toContain('starting'); + expect(host.textContent).toContain('Anthropic · sonnet · Medium'); expect(host.textContent).not.toContain('online'); expect(host.querySelector('.member-waiting-shimmer')).not.toBeNull(); - expect(host.querySelectorAll('.skeleton-shimmer').length).toBeGreaterThan(0); + expect(host.querySelectorAll('.skeleton-shimmer').length).toBe(0); await act(async () => { root.unmount();