fix(ci): restore workspace validation
This commit is contained in:
parent
d8672c32f6
commit
bb60bbb0ec
42 changed files with 208 additions and 201 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>(TeamGraphAdapter.create());
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import {
|
||||
DASHBOARD_RECENT_PROJECTS_ROUTE,
|
||||
normalizeDashboardRecentProjectsPayload,
|
||||
type DashboardRecentProjectsPayload,
|
||||
normalizeDashboardRecentProjectsPayload,
|
||||
} from '@features/recent-projects/contracts';
|
||||
import { createLogger } from '@shared/utils/logger';
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -180,8 +180,8 @@ import type {
|
|||
TeamSummary,
|
||||
TeamTask,
|
||||
TeamTaskStatus,
|
||||
TeamViewSnapshot,
|
||||
TeamUpdateConfigRequest,
|
||||
TeamViewSnapshot,
|
||||
ToolApprovalFileContent,
|
||||
ToolApprovalSettings,
|
||||
UpdateKanbanPatch,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
import { Badge } from '@renderer/components/ui/badge';
|
||||
import { useStore } from '@renderer/store';
|
||||
import {
|
||||
getInstallationSummaryLabel,
|
||||
getCapabilityLabel,
|
||||
getInstallationSummaryLabel,
|
||||
hasInstallationInScope,
|
||||
inferCapabilities,
|
||||
normalizeCategory,
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ import {
|
|||
} from '@renderer/components/ui/select';
|
||||
import { useStore } from '@renderer/store';
|
||||
import {
|
||||
getInstallationSummaryLabel,
|
||||
getCapabilityLabel,
|
||||
getInstallationSummaryLabel,
|
||||
hasInstallationInScope,
|
||||
inferCapabilities,
|
||||
normalizeCategory,
|
||||
|
|
|
|||
|
|
@ -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<CliProviderStatus, 'providerId' | 'authMethod' | 'backend'> | null;
|
||||
}): React.JSX.Element {
|
||||
}): React.JSX.Element => {
|
||||
const visibleModels = getVisibleTeamProviderModels(providerId, models, providerStatus);
|
||||
|
||||
return (
|
||||
|
|
@ -94,4 +94,4 @@ export function ProviderModelBadges({
|
|||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<TeamProvisioningPrepareResult>;
|
||||
}
|
||||
type PrepareProvisioningFn = (
|
||||
cwd?: string,
|
||||
providerId?: TeamProviderId,
|
||||
providerIds?: TeamProviderId[],
|
||||
selectedModels?: string[],
|
||||
limitContext?: boolean
|
||||
) => Promise<TeamProvisioningPrepareResult>;
|
||||
|
||||
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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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>): 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) {
|
||||
|
|
|
|||
|
|
@ -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<TimelineItem | null>(() => {
|
||||
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(() => {
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ export interface ExtensionsSlice {
|
|||
// Slice Creator
|
||||
// =============================================================================
|
||||
|
||||
let pluginFetchInFlight: { key: string; promise: Promise<void> } | null = null;
|
||||
let pluginFetchInFlight: { key: string; promise: Promise<void>; token: symbol } | null = null;
|
||||
let pluginCatalogRequestSeq = 0;
|
||||
const pluginSuccessResetTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
||||
let mcpDiagnosticsInFlight: Promise<void> | null = null;
|
||||
|
|
@ -286,6 +286,7 @@ export const createExtensionsSlice: StateCreator<AppState, [], [], ExtensionsSli
|
|||
}
|
||||
|
||||
const requestSeq = ++pluginCatalogRequestSeq;
|
||||
const requestToken = Symbol('pluginCatalogRequest');
|
||||
set({ pluginCatalogLoading: true, pluginCatalogError: null });
|
||||
|
||||
const promise = (async () => {
|
||||
|
|
@ -344,13 +345,13 @@ export const createExtensionsSlice: StateCreator<AppState, [], [], ExtensionsSli
|
|||
};
|
||||
});
|
||||
} finally {
|
||||
if (pluginFetchInFlight?.promise === promise) {
|
||||
if (pluginFetchInFlight?.token === requestToken) {
|
||||
pluginFetchInFlight = null;
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
pluginFetchInFlight = { key: requestKey, promise };
|
||||
pluginFetchInFlight = { key: requestKey, promise, token: requestToken };
|
||||
await promise;
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { api } from '@renderer/api';
|
||||
import { mergeTeamMessages } from '@renderer/utils/mergeTeamMessages';
|
||||
import { normalizePath } from '@renderer/utils/pathNormalize';
|
||||
import {
|
||||
buildTaskChangePresenceKey,
|
||||
|
|
@ -6,10 +7,9 @@ import {
|
|||
canDisplayTaskChangesForOptions,
|
||||
type TaskChangeRequestOptions,
|
||||
} from '@renderer/utils/taskChangeRequest';
|
||||
import { extractProviderScopedBaseModel } from '@renderer/utils/teamModelContext';
|
||||
import { toMessageKey } from '@renderer/utils/teamMessageKey';
|
||||
import { extractProviderScopedBaseModel } from '@renderer/utils/teamModelContext';
|
||||
import { IpcError, unwrapIpc } from '@renderer/utils/unwrapIpc';
|
||||
import { mergeTeamMessages } from '@renderer/utils/mergeTeamMessages';
|
||||
import { stripAgentBlocks } from '@shared/constants/agentBlocks';
|
||||
import { DEFAULT_TOOL_APPROVAL_SETTINGS } from '@shared/types/team';
|
||||
import { isLeadMember } from '@shared/utils/leadDetection';
|
||||
|
|
@ -111,10 +111,10 @@ type TeamGraphConfigMemberSeedInput = Pick<
|
|||
NonNullable<TeamViewSnapshot['config']['members']>[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<AppState, [], [], TeamSlice> = (set,
|
|||
|
||||
const existingOlderRequest = inFlightTeamMessagesOlderRequests.get(teamName);
|
||||
if (existingOlderRequest) {
|
||||
let queuedRequest: Promise<RefreshTeamMessagesHeadResult>;
|
||||
queuedRequest = existingOlderRequest
|
||||
const queuedRequest: Promise<RefreshTeamMessagesHeadResult> = existingOlderRequest
|
||||
.then(() => {
|
||||
if (queuedTeamMessagesHeadRefreshesAfterOlder.get(teamName) === queuedRequest) {
|
||||
queuedTeamMessagesHeadRefreshesAfterOlder.delete(teamName);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@
|
|||
|
||||
import type {
|
||||
CliInstallationStatus,
|
||||
InstallScope,
|
||||
InstalledPluginEntry,
|
||||
InstallScope,
|
||||
PluginCapability,
|
||||
PluginCatalogItem,
|
||||
} from '@shared/types';
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -49,6 +49,13 @@ function createDeferred<T>(): Deferred<T> {
|
|||
return { promise, resolve, reject };
|
||||
}
|
||||
|
||||
function createLogsFinderMock(listAttributedMemberFiles: ReturnType<typeof vi.fn>) {
|
||||
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)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Reference in a new issue