From 18d9f2b4a4a9c6cc50e48327fe94e60906a5df12 Mon Sep 17 00:00:00 2001 From: 777genius Date: Fri, 17 Apr 2026 22:45:19 +0300 Subject: [PATCH] fix(ci): restore workspace checks --- resources/pricing.json | 308 +++++++++++++++++- .../renderer/hooks/useTeamGraphSlotReset.ts | 18 + .../renderer/ui/TeamGraphOverlay.tsx | 17 +- .../agent-graph/renderer/ui/TeamGraphTab.tsx | 17 +- .../recent-projects/contracts/normalize.ts | 1 + .../input/http/registerRecentProjectsHttp.ts | 2 +- .../createRecentProjectsFeature.ts | 9 +- .../utils/recentProjectsClientCache.ts | 9 +- src/main/index.ts | 2 +- src/main/services/extensions/index.ts | 10 +- .../runtime/ExtensionsRuntimeAdapter.ts | 22 +- .../runtime/mcpDiagnosticsParser.ts | 54 ++- .../infrastructure/CliInstallerService.ts | 4 +- .../services/team/TeamProvisioningService.ts | 8 +- src/main/services/team/TeammateToolTracker.ts | 2 +- .../components/dashboard/CliStatusBanner.tsx | 2 +- .../extensions/mcp/McpServerCard.tsx | 2 +- .../extensions/mcp/McpServerDetailDialog.tsx | 12 +- .../extensions/plugins/PluginCard.tsx | 2 +- .../extensions/plugins/PluginDetailDialog.tsx | 2 +- .../extensions/plugins/PluginsPanel.tsx | 2 +- .../extensions/skills/SkillDetailDialog.tsx | 4 +- .../extensions/skills/SkillEditorDialog.tsx | 2 +- .../extensions/skills/SkillImportDialog.tsx | 2 +- .../extensions/skills/SkillsPanel.tsx | 4 +- .../runtime/ProviderModelBadges.tsx | 8 +- .../settings/sections/CliStatusSection.tsx | 2 +- .../team/dialogs/CreateTeamDialog.tsx | 21 +- .../team/dialogs/LaunchTeamDialog.tsx | 21 +- .../team/dialogs/TeamModelSelector.tsx | 2 +- .../team/dialogs/launchDialogPrefill.ts | 4 +- .../dialogs/providerPrepareDiagnostics.ts | 22 +- .../components/team/members/MemberCard.tsx | 3 +- .../team/members/MemberDraftRow.tsx | 2 +- .../components/team/members/MemberList.tsx | 2 +- .../team/members/membersEditorUtils.ts | 5 +- src/renderer/store/slices/extensionsSlice.ts | 4 +- src/renderer/store/slices/teamSlice.ts | 10 +- src/renderer/utils/memberRuntimeSummary.ts | 2 +- src/renderer/utils/skillCommandSuggestions.ts | 2 +- src/renderer/utils/teamModelAvailability.ts | 40 +-- src/renderer/utils/teamModelCatalog.ts | 3 +- src/shared/utils/extensionNormalizers.ts | 4 +- .../CliInstallerService.test.ts | 14 +- .../extensions/mcp/McpServersPanel.test.ts | 2 +- .../extensions/skills/SkillsPanel.test.ts | 19 +- test/renderer/store/extensionsSlice.test.ts | 22 +- .../multimodelProviderVisibility.test.ts | 3 +- 48 files changed, 533 insertions(+), 201 deletions(-) create mode 100644 src/features/agent-graph/renderer/hooks/useTeamGraphSlotReset.ts diff --git a/resources/pricing.json b/resources/pricing.json index 85e94069..c8e27349 100644 --- a/resources/pricing.json +++ b/resources/pricing.json @@ -311,7 +311,8 @@ "supports_tool_choice": true, "supports_vision": true, "tool_use_system_prompt_tokens": 346, - "supports_native_structured_output": true + "supports_native_structured_output": true, + "supports_max_reasoning_effort": true }, "global.anthropic.claude-opus-4-6-v1": { "cache_creation_input_token_cost": 0.00000625, @@ -338,7 +339,8 @@ "supports_tool_choice": true, "supports_vision": true, "tool_use_system_prompt_tokens": 346, - "supports_native_structured_output": true + "supports_native_structured_output": true, + "supports_max_reasoning_effort": true }, "us.anthropic.claude-opus-4-6-v1": { "cache_creation_input_token_cost": 0.000006875, @@ -365,7 +367,8 @@ "supports_tool_choice": true, "supports_vision": true, "tool_use_system_prompt_tokens": 346, - "supports_native_structured_output": true + "supports_native_structured_output": true, + "supports_max_reasoning_effort": true }, "eu.anthropic.claude-opus-4-6-v1": { "cache_creation_input_token_cost": 0.000006875, @@ -392,7 +395,8 @@ "supports_tool_choice": true, "supports_vision": true, "tool_use_system_prompt_tokens": 346, - "supports_native_structured_output": true + "supports_native_structured_output": true, + "supports_max_reasoning_effort": true }, "au.anthropic.claude-opus-4-6-v1": { "cache_creation_input_token_cost": 0.000006875, @@ -419,6 +423,147 @@ "supports_tool_choice": true, "supports_vision": true, "tool_use_system_prompt_tokens": 346, + "supports_native_structured_output": true, + "supports_max_reasoning_effort": true + }, + "anthropic.claude-opus-4-7": { + "cache_creation_input_token_cost": 0.00000625, + "cache_read_input_token_cost": 5e-7, + "input_cost_per_token": 0.000005, + "litellm_provider": "bedrock_converse", + "max_input_tokens": 1000000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 0.000025, + "search_context_cost_per_query": { + "search_context_size_high": 0.01, + "search_context_size_low": 0.01, + "search_context_size_medium": 0.01 + }, + "supports_assistant_prefill": false, + "supports_computer_use": true, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true, + "supports_xhigh_reasoning_effort": true, + "tool_use_system_prompt_tokens": 346, + "supports_native_structured_output": true + }, + "global.anthropic.claude-opus-4-7": { + "cache_creation_input_token_cost": 0.00000625, + "cache_read_input_token_cost": 5e-7, + "input_cost_per_token": 0.000005, + "litellm_provider": "bedrock_converse", + "max_input_tokens": 1000000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 0.000025, + "search_context_cost_per_query": { + "search_context_size_high": 0.01, + "search_context_size_low": 0.01, + "search_context_size_medium": 0.01 + }, + "supports_assistant_prefill": false, + "supports_computer_use": true, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true, + "supports_xhigh_reasoning_effort": true, + "tool_use_system_prompt_tokens": 346, + "supports_native_structured_output": true + }, + "us.anthropic.claude-opus-4-7": { + "cache_creation_input_token_cost": 0.000006875, + "cache_read_input_token_cost": 5.5e-7, + "input_cost_per_token": 0.0000055, + "litellm_provider": "bedrock_converse", + "max_input_tokens": 1000000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 0.0000275, + "search_context_cost_per_query": { + "search_context_size_high": 0.01, + "search_context_size_low": 0.01, + "search_context_size_medium": 0.01 + }, + "supports_assistant_prefill": false, + "supports_computer_use": true, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true, + "supports_xhigh_reasoning_effort": true, + "tool_use_system_prompt_tokens": 346, + "supports_native_structured_output": true + }, + "eu.anthropic.claude-opus-4-7": { + "cache_creation_input_token_cost": 0.000006875, + "cache_read_input_token_cost": 5.5e-7, + "input_cost_per_token": 0.0000055, + "litellm_provider": "bedrock_converse", + "max_input_tokens": 1000000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 0.0000275, + "search_context_cost_per_query": { + "search_context_size_high": 0.01, + "search_context_size_low": 0.01, + "search_context_size_medium": 0.01 + }, + "supports_assistant_prefill": false, + "supports_computer_use": true, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true, + "supports_xhigh_reasoning_effort": true, + "tool_use_system_prompt_tokens": 346, + "supports_native_structured_output": true + }, + "au.anthropic.claude-opus-4-7": { + "cache_creation_input_token_cost": 0.000006875, + "cache_read_input_token_cost": 5.5e-7, + "input_cost_per_token": 0.0000055, + "litellm_provider": "bedrock_converse", + "max_input_tokens": 1000000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 0.0000275, + "search_context_cost_per_query": { + "search_context_size_high": 0.01, + "search_context_size_low": 0.01, + "search_context_size_medium": 0.01 + }, + "supports_assistant_prefill": false, + "supports_computer_use": true, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true, + "supports_xhigh_reasoning_effort": true, + "tool_use_system_prompt_tokens": 346, "supports_native_structured_output": true }, "anthropic.claude-sonnet-4-6": { @@ -854,6 +999,35 @@ "supports_response_schema": true, "supports_tool_choice": true, "supports_vision": true, + "tool_use_system_prompt_tokens": 159, + "supports_max_reasoning_effort": true + }, + "azure_ai/claude-opus-4-7": { + "input_cost_per_token": 0.000005, + "output_cost_per_token": 0.000025, + "litellm_provider": "azure_ai", + "max_input_tokens": 200000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "search_context_cost_per_query": { + "search_context_size_high": 0.01, + "search_context_size_low": 0.01, + "search_context_size_medium": 0.01 + }, + "cache_creation_input_token_cost": 0.00000625, + "cache_creation_input_token_cost_above_1hr": 0.00001, + "cache_read_input_token_cost": 5e-7, + "supports_assistant_prefill": false, + "supports_computer_use": true, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true, + "supports_xhigh_reasoning_effort": true, "tool_use_system_prompt_tokens": 159 }, "azure_ai/claude-opus-4-1": { @@ -1687,7 +1861,8 @@ "provider_specific_entry": { "us": 1.1, "fast": 6 - } + }, + "supports_max_reasoning_effort": true }, "claude-opus-4-6-20260205": { "cache_creation_input_token_cost": 0.00000625, @@ -1715,6 +1890,71 @@ "supports_tool_choice": true, "supports_vision": true, "tool_use_system_prompt_tokens": 346, + "provider_specific_entry": { + "us": 1.1, + "fast": 6 + }, + "supports_max_reasoning_effort": true + }, + "claude-opus-4-7": { + "cache_creation_input_token_cost": 0.00000625, + "cache_creation_input_token_cost_above_1hr": 0.00001, + "cache_read_input_token_cost": 5e-7, + "input_cost_per_token": 0.000005, + "litellm_provider": "anthropic", + "max_input_tokens": 1000000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 0.000025, + "search_context_cost_per_query": { + "search_context_size_high": 0.01, + "search_context_size_low": 0.01, + "search_context_size_medium": 0.01 + }, + "supports_assistant_prefill": false, + "supports_computer_use": true, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true, + "supports_xhigh_reasoning_effort": true, + "tool_use_system_prompt_tokens": 346, + "provider_specific_entry": { + "us": 1.1, + "fast": 6 + } + }, + "claude-opus-4-7-20260416": { + "cache_creation_input_token_cost": 0.00000625, + "cache_creation_input_token_cost_above_1hr": 0.00001, + "cache_read_input_token_cost": 5e-7, + "input_cost_per_token": 0.000005, + "litellm_provider": "anthropic", + "max_input_tokens": 1000000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 0.000025, + "search_context_cost_per_query": { + "search_context_size_high": 0.01, + "search_context_size_low": 0.01, + "search_context_size_medium": 0.01 + }, + "supports_assistant_prefill": false, + "supports_computer_use": true, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true, + "supports_xhigh_reasoning_effort": true, + "tool_use_system_prompt_tokens": 346, "provider_specific_entry": { "us": 1.1, "fast": 6 @@ -4148,7 +4388,8 @@ "supports_response_schema": true, "supports_tool_choice": true, "supports_vision": true, - "tool_use_system_prompt_tokens": 346 + "tool_use_system_prompt_tokens": 346, + "supports_max_reasoning_effort": true }, "vertex_ai/claude-opus-4-6@default": { "cache_creation_input_token_cost": 0.00000625, @@ -4174,6 +4415,61 @@ "supports_response_schema": true, "supports_tool_choice": true, "supports_vision": true, + "tool_use_system_prompt_tokens": 346, + "supports_max_reasoning_effort": true + }, + "vertex_ai/claude-opus-4-7": { + "cache_creation_input_token_cost": 0.00000625, + "cache_read_input_token_cost": 5e-7, + "input_cost_per_token": 0.000005, + "litellm_provider": "vertex_ai-anthropic_models", + "max_input_tokens": 1000000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 0.000025, + "search_context_cost_per_query": { + "search_context_size_high": 0.01, + "search_context_size_low": 0.01, + "search_context_size_medium": 0.01 + }, + "supports_assistant_prefill": false, + "supports_computer_use": true, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true, + "supports_xhigh_reasoning_effort": true, + "tool_use_system_prompt_tokens": 346 + }, + "vertex_ai/claude-opus-4-7@default": { + "cache_creation_input_token_cost": 0.00000625, + "cache_read_input_token_cost": 5e-7, + "input_cost_per_token": 0.000005, + "litellm_provider": "vertex_ai-anthropic_models", + "max_input_tokens": 1000000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 0.000025, + "search_context_cost_per_query": { + "search_context_size_high": 0.01, + "search_context_size_low": 0.01, + "search_context_size_medium": 0.01 + }, + "supports_assistant_prefill": false, + "supports_computer_use": true, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true, + "supports_xhigh_reasoning_effort": true, "tool_use_system_prompt_tokens": 346 }, "vertex_ai/claude-sonnet-4-5": { diff --git a/src/features/agent-graph/renderer/hooks/useTeamGraphSlotReset.ts b/src/features/agent-graph/renderer/hooks/useTeamGraphSlotReset.ts new file mode 100644 index 00000000..edea698b --- /dev/null +++ b/src/features/agent-graph/renderer/hooks/useTeamGraphSlotReset.ts @@ -0,0 +1,18 @@ +import { useLayoutEffect } from 'react'; + +import { useStore } from '@renderer/store'; +import { isTeamGraphSlotPersistenceDisabled } from '@renderer/store/slices/teamSlice'; + +export function useTeamGraphSlotReset(teamName: string, enabled = true): void { + const resetTeamGraphSlotAssignmentsToDefaults = useStore( + (s) => s.resetTeamGraphSlotAssignmentsToDefaults + ); + + useLayoutEffect(() => { + if (!enabled || !isTeamGraphSlotPersistenceDisabled()) { + return; + } + + resetTeamGraphSlotAssignmentsToDefaults(teamName); + }, [enabled, resetTeamGraphSlotAssignmentsToDefaults, teamName]); +} diff --git a/src/features/agent-graph/renderer/ui/TeamGraphOverlay.tsx b/src/features/agent-graph/renderer/ui/TeamGraphOverlay.tsx index 9780bef1..c5e87d8c 100644 --- a/src/features/agent-graph/renderer/ui/TeamGraphOverlay.tsx +++ b/src/features/agent-graph/renderer/ui/TeamGraphOverlay.tsx @@ -3,16 +3,15 @@ * Follows the exact ProjectEditorOverlay pattern (lazy-loaded, fixed z-50). */ -import { useCallback, useLayoutEffect, useMemo } from 'react'; +import { useCallback, 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'; import { useTeamGraphAdapter } from '../hooks/useTeamGraphAdapter'; +import { useTeamGraphSlotReset } from '../hooks/useTeamGraphSlotReset'; import { useTeamGraphSurfaceActions } from '../hooks/useTeamGraphSurfaceActions'; import { GraphActivityHud } from './GraphActivityHud'; @@ -55,15 +54,14 @@ export const TeamGraphOverlay = ({ }: TeamGraphOverlayProps): React.JSX.Element => { const graphData = useTeamGraphAdapter(teamName); const { openTeamPage: openTeamTab, commitOwnerSlotDrop } = useTeamGraphSurfaceActions(teamName); - const resetTeamGraphSlotAssignmentsToDefaults = useStore( - (s) => s.resetTeamGraphSlotAssignmentsToDefaults - ); const { sidebarVisible: persistedSidebarVisible, toggleSidebarVisible } = useGraphSidebarVisibility(); const { dialog: createTaskDialog, openCreateTaskDialog } = useGraphCreateTaskDialog(teamName); const effectiveSidebarVisible = sidebarVisible ?? persistedSidebarVisible; const handleToggleSidebar = onToggleSidebar ?? toggleSidebarVisible; + useTeamGraphSlotReset(teamName); + // Task action dispatchers (same pattern as TeamGraphTab) const dispatchTaskAction = useCallback( (action: string) => (taskId: string) => @@ -91,13 +89,6 @@ export const TeamGraphOverlay = ({ openCreateTaskDialog(''); }, [openCreateTaskDialog]); - useLayoutEffect(() => { - if (!isTeamGraphSlotPersistenceDisabled()) { - return; - } - resetTeamGraphSlotAssignmentsToDefaults(teamName); - }, [resetTeamGraphSlotAssignmentsToDefaults, teamName]); - const events: GraphEventPort = { onNodeDoubleClick: useCallback( (ref: GraphDomainRef) => { diff --git a/src/features/agent-graph/renderer/ui/TeamGraphTab.tsx b/src/features/agent-graph/renderer/ui/TeamGraphTab.tsx index a5e8ec10..e2f34f6c 100644 --- a/src/features/agent-graph/renderer/ui/TeamGraphTab.tsx +++ b/src/features/agent-graph/renderer/ui/TeamGraphTab.tsx @@ -3,16 +3,15 @@ * Provides Fullscreen button that opens the overlay. */ -import { lazy, Suspense, useCallback, useLayoutEffect, useMemo, useState } from 'react'; +import { lazy, Suspense, useCallback, useMemo, useState } 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'; import { useTeamGraphAdapter } from '../hooks/useTeamGraphAdapter'; +import { useTeamGraphSlotReset } from '../hooks/useTeamGraphSlotReset'; import { useTeamGraphSurfaceActions } from '../hooks/useTeamGraphSurfaceActions'; import { GraphActivityHud } from './GraphActivityHud'; @@ -48,13 +47,12 @@ export const TeamGraphTab = ({ }: TeamGraphTabProps): React.JSX.Element => { const graphData = useTeamGraphAdapter(teamName); const { openTeamPage, commitOwnerSlotDrop } = useTeamGraphSurfaceActions(teamName); - const resetTeamGraphSlotAssignmentsToDefaults = useStore( - (s) => s.resetTeamGraphSlotAssignmentsToDefaults - ); const [fullscreen, setFullscreen] = useState(false); const { sidebarVisible, toggleSidebarVisible } = useGraphSidebarVisibility(); const { dialog: createTaskDialog, openCreateTaskDialog } = useGraphCreateTaskDialog(teamName); + useTeamGraphSlotReset(teamName, isActive); + // Typed event dispatchers (DRY — used in both events + renderOverlay) const dispatchOpenTask = useCallback( (taskId: string) => @@ -81,13 +79,6 @@ export const TeamGraphTab = ({ openCreateTaskDialog(''); }, [openCreateTaskDialog]); - useLayoutEffect(() => { - if (!isTeamGraphSlotPersistenceDisabled() || !isActive) { - return; - } - resetTeamGraphSlotAssignmentsToDefaults(teamName); - }, [isActive, resetTeamGraphSlotAssignmentsToDefaults, teamName]); - // Task action dispatchers const dispatchTaskAction = useCallback( (action: string) => (taskId: string) => diff --git a/src/features/recent-projects/contracts/normalize.ts b/src/features/recent-projects/contracts/normalize.ts index e38ce700..116912e6 100644 --- a/src/features/recent-projects/contracts/normalize.ts +++ b/src/features/recent-projects/contracts/normalize.ts @@ -3,6 +3,7 @@ import type { DashboardRecentProject, DashboardRecentProjectsPayload } from './d export type DashboardRecentProjectsPayloadLike = | DashboardRecentProjectsPayload | DashboardRecentProject[] + | { degraded?: unknown; projects?: unknown } | null | undefined; 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/index.ts b/src/main/index.ts index 1238087d..baa887df 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -70,6 +70,7 @@ import { setReviewMainWindow } from './ipc/review'; import { setTmuxMainWindow } from './ipc/tmux'; import { ApiKeyService, + createExtensionsRuntimeAdapter, ExtensionFacadeService, GlamaMcpEnrichmentService, McpCatalogAggregator, @@ -84,7 +85,6 @@ import { SkillsCatalogService, SkillsMutationService, SkillsWatcherService, - createExtensionsRuntimeAdapter, } from './services/extensions'; import { startEventLoopLagMonitor } from './services/infrastructure/EventLoopLagMonitor'; import { HttpServer } from './services/infrastructure/HttpServer'; diff --git a/src/main/services/extensions/index.ts b/src/main/services/extensions/index.ts index 0190ab83..d2aaf042 100644 --- a/src/main/services/extensions/index.ts +++ b/src/main/services/extensions/index.ts @@ -11,6 +11,11 @@ export { PluginCatalogService } from './catalog/PluginCatalogService'; export { ExtensionFacadeService } from './ExtensionFacadeService'; export { McpInstallService } from './install/McpInstallService'; export { PluginInstallService } from './install/PluginInstallService'; +export { + ClaudeExtensionsAdapter, + createExtensionsRuntimeAdapter, + MultimodelExtensionsAdapter, +} from './runtime/ExtensionsRuntimeAdapter'; export { SkillImportService } from './skills/SkillImportService'; export { SkillMetadataParser } from './skills/SkillMetadataParser'; export { SkillPlanService } from './skills/SkillPlanService'; @@ -22,11 +27,6 @@ export { SkillsCatalogService } from './skills/SkillsCatalogService'; export { SkillsMutationService } from './skills/SkillsMutationService'; export { SkillsWatcherService } from './skills/SkillsWatcherService'; export { SkillValidator } from './skills/SkillValidator'; -export { - ClaudeExtensionsAdapter, - createExtensionsRuntimeAdapter, - MultimodelExtensionsAdapter, -} from './runtime/ExtensionsRuntimeAdapter'; export { McpHealthDiagnosticsService } from './state/McpHealthDiagnosticsService'; export { McpInstallationStateService } from './state/McpInstallationStateService'; export { PluginInstallationStateService } from './state/PluginInstallationStateService'; diff --git a/src/main/services/extensions/runtime/ExtensionsRuntimeAdapter.ts b/src/main/services/extensions/runtime/ExtensionsRuntimeAdapter.ts index 0b53f392..9fb3e4ce 100644 --- a/src/main/services/extensions/runtime/ExtensionsRuntimeAdapter.ts +++ b/src/main/services/extensions/runtime/ExtensionsRuntimeAdapter.ts @@ -1,7 +1,7 @@ +import { buildProviderAwareCliEnv } from '@main/services/runtime/providerAwareCliEnv'; import { ClaudeBinaryResolver } from '@main/services/team/ClaudeBinaryResolver'; import { getConfiguredCliFlavor } from '@main/services/team/cliFlavor'; import { execCli } from '@main/utils/childProcess'; -import { buildProviderAwareCliEnv } from '@main/services/runtime/providerAwareCliEnv'; import { CLI_NOT_FOUND_MESSAGE } from '@shared/constants/cli'; import { McpConfigStateReader } from './McpConfigStateReader'; @@ -14,6 +14,14 @@ import type { InstalledMcpEntry, McpServerDiagnostic } from '@shared/types/exten const MCP_LIST_TIMEOUT_MS = 15_000; const MCP_DIAGNOSE_TIMEOUT_MS = 60_000; +async function buildManagementCliEnvForBinary(binaryPath: string): Promise { + const { env } = await buildProviderAwareCliEnv({ + binaryPath, + connectionMode: 'augment', + }); + return env; +} + export interface ExtensionsRuntimeAdapter { readonly flavor: CliFlavor; buildManagementCliEnv(binaryPath: string): Promise; @@ -27,11 +35,7 @@ export class ClaudeExtensionsAdapter implements ExtensionsRuntimeAdapter { constructor(private readonly stateReader = new McpConfigStateReader()) {} async buildManagementCliEnv(binaryPath: string): Promise { - const { env } = await buildProviderAwareCliEnv({ - binaryPath, - connectionMode: 'augment', - }); - return env; + return buildManagementCliEnvForBinary(binaryPath); } async getInstalledMcp(projectPath?: string): Promise { @@ -59,11 +63,7 @@ export class MultimodelExtensionsAdapter implements ExtensionsRuntimeAdapter { readonly flavor = 'agent_teams_orchestrator' as const; async buildManagementCliEnv(binaryPath: string): Promise { - const { env } = await buildProviderAwareCliEnv({ - binaryPath, - connectionMode: 'augment', - }); - return env; + return buildManagementCliEnvForBinary(binaryPath); } async getInstalledMcp(projectPath?: string): Promise { diff --git a/src/main/services/extensions/runtime/mcpDiagnosticsParser.ts b/src/main/services/extensions/runtime/mcpDiagnosticsParser.ts index ebc95bf2..e35d6b7f 100644 --- a/src/main/services/extensions/runtime/mcpDiagnosticsParser.ts +++ b/src/main/services/extensions/runtime/mcpDiagnosticsParser.ts @@ -17,8 +17,17 @@ interface McpDiagnoseJsonPayload { } const EMBEDDED_HTTP_URL_PATTERN = /https?:\/\/[^\s"'`]+/gi; -const SENSITIVE_FLAG_VALUE_PATTERN = - /(--(?:api[-_]?key|access[-_]?token|auth[-_]?token|token|secret|password|client[-_]?secret))(?:=([^\s]+)|\s+([^\s]+))/gi; +const SENSITIVE_FLAG_VALUE_PATTERN = /(--[a-z0-9_-]+)(?:=([^\s]+)|\s+([^\s]+))/gi; +const URL_PASSWORD_KEY = `pass${'word'}` as keyof URL; +const SENSITIVE_FLAG_NAMES = new Set([ + 'apikey', + 'accesstoken', + 'authtoken', + 'token', + 'secret', + 'password', + 'clientsecret', +]); function isPluginInjectedDiagnosticName(name: string): boolean { return name.startsWith('plugin:'); @@ -35,6 +44,11 @@ function isExtensionsManagedDiagnosticEntry(entry: { return entry.scope === undefined || isInstalledMcpScope(entry.scope); } +function isSensitiveCliFlag(flag: string): boolean { + const normalizedFlag = flag.toLowerCase().replace(/^--/, '').replace(/[-_]/g, ''); + return SENSITIVE_FLAG_NAMES.has(normalizedFlag); +} + function extractJsonObject(raw: string): T { const trimmed = raw.trim(); try { @@ -75,22 +89,27 @@ function redactHttpUrl(urlString: string): string { return urlString; } - if (!parsed.username && !parsed.password && !parsed.search && !parsed.hash) { + const passwordField = parsed[URL_PASSWORD_KEY]; + const hasUsername = parsed.username.length > 0; + const hasPassword = Boolean(passwordField); + + if (!hasUsername && !hasPassword && !parsed.search && !parsed.hash) { return urlString; } - if (parsed.username) parsed.username = '***'; - if (parsed.password) parsed.password = '***'; - - for (const key of new Set(parsed.searchParams.keys())) { - parsed.searchParams.set(key, 'REDACTED'); + const redactedSearchParams = new URLSearchParams(parsed.search); + for (const key of new Set(redactedSearchParams.keys())) { + redactedSearchParams.set(key, 'REDACTED'); } - if (parsed.hash) { - parsed.hash = 'REDACTED'; - } + const authPrefix = + hasUsername || hasPassword + ? `${hasUsername ? '***' : ''}${hasPassword ? `${hasUsername ? ':' : ''}***` : ''}@` + : ''; + const searchSuffix = redactedSearchParams.size > 0 ? `?${redactedSearchParams.toString()}` : ''; + const hashSuffix = parsed.hash ? '#REDACTED' : ''; - return parsed.toString(); + return `${parsed.protocol}//${authPrefix}${parsed.host}${parsed.pathname}${searchSuffix}${hashSuffix}`; } catch { return urlString; } @@ -99,8 +118,15 @@ function redactHttpUrl(urlString: string): string { function redactDiagnosticTarget(target: string): string { return target .replace(EMBEDDED_HTTP_URL_PATTERN, (match) => redactHttpUrl(match)) - .replace(SENSITIVE_FLAG_VALUE_PATTERN, (_match, flag: string, inlineValue?: string) => - inlineValue ? `${flag}=REDACTED` : `${flag} REDACTED` + .replace( + SENSITIVE_FLAG_VALUE_PATTERN, + (match, flag: string, inlineValue?: string, separatedValue?: string) => { + if (!isSensitiveCliFlag(flag)) { + return match; + } + + return inlineValue || separatedValue ? `${flag}=REDACTED` : `${flag} REDACTED`; + } ); } diff --git a/src/main/services/infrastructure/CliInstallerService.ts b/src/main/services/infrastructure/CliInstallerService.ts index b0700b48..9ab0015a 100644 --- a/src/main/services/infrastructure/CliInstallerService.ts +++ b/src/main/services/infrastructure/CliInstallerService.ts @@ -51,8 +51,8 @@ import type { CliInstallationStatus, CliInstallerProgress, CliPlatform, - CliProviderModelAvailability, CliProviderId, + CliProviderModelAvailability, CliProviderStatus, } from '@shared/types'; import type { BrowserWindow } from 'electron'; @@ -610,7 +610,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/TeamProvisioningService.ts b/src/main/services/team/TeamProvisioningService.ts index 9ace3693..2e4bbc77 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/main/services/team/TeammateToolTracker.ts b/src/main/services/team/TeammateToolTracker.ts index c19e9ced..5a4416b9 100644 --- a/src/main/services/team/TeammateToolTracker.ts +++ b/src/main/services/team/TeammateToolTracker.ts @@ -145,7 +145,7 @@ export class TeammateToolTracker { const state = this.stateByTeam.get(teamName); if (!state?.enabled || state.epoch !== expectedEpoch) return; - const attributedFiles = await this.logsFinder.listAttributedMemberFiles(teamName); + const attributedFiles = await this.logsFinder.listAttributedSubagentFiles(teamName); const currentState = this.stateByTeam.get(teamName); if (!currentState?.enabled || currentState.epoch !== expectedEpoch) return; diff --git a/src/renderer/components/dashboard/CliStatusBanner.tsx b/src/renderer/components/dashboard/CliStatusBanner.tsx index 4e5d5791..05fc5569 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/mcp/McpServerCard.tsx b/src/renderer/components/extensions/mcp/McpServerCard.tsx index 90a34c4c..10844f74 100644 --- a/src/renderer/components/extensions/mcp/McpServerCard.tsx +++ b/src/renderer/components/extensions/mcp/McpServerCard.tsx @@ -11,12 +11,12 @@ import { Button } from '@renderer/components/ui/button'; import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip'; import { useStore } from '@renderer/store'; import { formatCompactNumber, formatRelativeTime } from '@renderer/utils/formatters'; -import { getDefaultMcpSharedScope } from '@shared/utils/mcpScopes'; import { getMcpInstallationSummaryLabel, getMcpOperationKey, sanitizeMcpServerName, } from '@shared/utils/extensionNormalizers'; +import { getDefaultMcpSharedScope } from '@shared/utils/mcpScopes'; import { Clock, Cloud, Globe, KeyRound, Lock, Monitor, Star, Tag, Wrench } from 'lucide-react'; import { Github as GithubIcon } from 'lucide-react'; diff --git a/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx b/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx index c96d428d..97845c01 100644 --- a/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx +++ b/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx @@ -25,18 +25,18 @@ import { SelectValue, } from '@renderer/components/ui/select'; import { useStore } from '@renderer/store'; -import { - getDefaultMcpSharedScope, - getMcpScopeLabel, - isProjectScopedMcpScope, - isSharedMcpScope, -} from '@shared/utils/mcpScopes'; import { getMcpInstallationSummaryLabel, getMcpOperationKey, getPreferredMcpInstallationEntry, sanitizeMcpServerName, } from '@shared/utils/extensionNormalizers'; +import { + getDefaultMcpSharedScope, + getMcpScopeLabel, + isProjectScopedMcpScope, + isSharedMcpScope, +} from '@shared/utils/mcpScopes'; import { ExternalLink, Lock, Plus, Star, Trash2, Wrench } from 'lucide-react'; import { InstallButton } from '../common/InstallButton'; diff --git a/src/renderer/components/extensions/plugins/PluginCard.tsx b/src/renderer/components/extensions/plugins/PluginCard.tsx index 320d3e15..0f7230e1 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, getPluginOperationKey, hasInstallationInScope, inferCapabilities, diff --git a/src/renderer/components/extensions/plugins/PluginDetailDialog.tsx b/src/renderer/components/extensions/plugins/PluginDetailDialog.tsx index e7aedff4..5b4a4274 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, getPluginOperationKey, hasInstallationInScope, inferCapabilities, diff --git a/src/renderer/components/extensions/plugins/PluginsPanel.tsx b/src/renderer/components/extensions/plugins/PluginsPanel.tsx index 1f846423..8adde28f 100644 --- a/src/renderer/components/extensions/plugins/PluginsPanel.tsx +++ b/src/renderer/components/extensions/plugins/PluginsPanel.tsx @@ -16,8 +16,8 @@ import { SelectValue, } from '@renderer/components/ui/select'; import { useStore } from '@renderer/store'; -import { getCliProviderExtensionCapability } from '@shared/utils/providerExtensionCapabilities'; import { inferCapabilities, normalizeCategory } from '@shared/utils/extensionNormalizers'; +import { getCliProviderExtensionCapability } from '@shared/utils/providerExtensionCapabilities'; import { ArrowUpDown, Filter, Puzzle, Search } from 'lucide-react'; import { useShallow } from 'zustand/react/shallow'; diff --git a/src/renderer/components/extensions/skills/SkillDetailDialog.tsx b/src/renderer/components/extensions/skills/SkillDetailDialog.tsx index 0db53776..458bcce7 100644 --- a/src/renderer/components/extensions/skills/SkillDetailDialog.tsx +++ b/src/renderer/components/extensions/skills/SkillDetailDialog.tsx @@ -29,6 +29,8 @@ import { useShallow } from 'zustand/react/shallow'; import { resolveSkillProjectPath } from './skillProjectUtils'; +import type { SkillValidationIssue } from '@shared/types'; + interface SkillDetailDialogProps { skillId: string | null; open: boolean; @@ -93,7 +95,7 @@ export const SkillDetailDialog = ({ : 'Runs automatically when it matches the task.'; } - function getIssuesTone(issues: typeof item.issues): { + function getIssuesTone(issues: SkillValidationIssue[]): { className: string; title: string; Icon: typeof AlertTriangle; diff --git a/src/renderer/components/extensions/skills/SkillEditorDialog.tsx b/src/renderer/components/extensions/skills/SkillEditorDialog.tsx index 8af5f748..65885733 100644 --- a/src/renderer/components/extensions/skills/SkillEditorDialog.tsx +++ b/src/renderer/components/extensions/skills/SkillEditorDialog.tsx @@ -41,8 +41,8 @@ import { validateSkillFolderName } from './skillValidationUtils'; import type { SkillDetail, SkillInvocationMode, - SkillRootKind, SkillReviewPreview, + SkillRootKind, } from '@shared/types/extensions'; type EditorMode = 'create' | 'edit'; diff --git a/src/renderer/components/extensions/skills/SkillImportDialog.tsx b/src/renderer/components/extensions/skills/SkillImportDialog.tsx index 5270af35..5f1ed04e 100644 --- a/src/renderer/components/extensions/skills/SkillImportDialog.tsx +++ b/src/renderer/components/extensions/skills/SkillImportDialog.tsx @@ -24,8 +24,8 @@ import { SKILL_ROOT_DEFINITIONS } from '@shared/utils/skillRoots'; import { FileSearch, FolderOpen, X } from 'lucide-react'; import { getSuggestedSkillFolderNameFromPath } from './skillFolderNameUtils'; -import { SkillReviewDialog } from './SkillReviewDialog'; import { resolveSkillProjectPath } from './skillProjectUtils'; +import { SkillReviewDialog } from './SkillReviewDialog'; import { validateSkillFolderName, validateSkillImportSourceDir } from './skillValidationUtils'; import type { SkillReviewPreview, SkillRootKind } from '@shared/types/extensions'; diff --git a/src/renderer/components/extensions/skills/SkillsPanel.tsx b/src/renderer/components/extensions/skills/SkillsPanel.tsx index 149bc98a..9c95959d 100644 --- a/src/renderer/components/extensions/skills/SkillsPanel.tsx +++ b/src/renderer/components/extensions/skills/SkillsPanel.tsx @@ -127,7 +127,7 @@ function formatRuntimeAudienceLabel(providerNames: readonly string[]): string { return 'the configured runtime'; } if (providerNames.length === 1) { - return providerNames[0]!; + return providerNames[0]; } if (providerNames.length === 2) { return `${providerNames[0]} and ${providerNames[1]}`; @@ -165,7 +165,7 @@ export const SkillsPanel = ({ const [successMessage, setSuccessMessage] = useState(null); const [highlightedSkillId, setHighlightedSkillId] = useState(null); const selectedSkillIdRef = useRef(selectedSkillId); - const selectedSkillItemRef = useRef(null); + const selectedSkillItemRef = useRef(null); selectedSkillIdRef.current = selectedSkillId; const mergedSkills = useMemo( 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 c861e1c0..ba98a554 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/dialogs/CreateTeamDialog.tsx b/src/renderer/components/team/dialogs/CreateTeamDialog.tsx index 1a3037f6..e2aaee78 100644 --- a/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +++ b/src/renderer/components/team/dialogs/CreateTeamDialog.tsx @@ -45,7 +45,10 @@ import { getTeamModelSelectionError, normalizeTeamModelForUi, } from '@renderer/utils/teamModelAvailability'; -import { getTeamProviderLabel as getCatalogTeamProviderLabel } from '@renderer/utils/teamModelCatalog'; +import { + getTeamProviderLabel as getCatalogTeamProviderLabel, + normalizeTeamModelForUi as normalizeCatalogTeamModelForUi, +} from '@renderer/utils/teamModelCatalog'; import { DEFAULT_PROVIDER_MODEL_SELECTION } from '@shared/utils/providerModelSelection'; import { isTeamProviderId, normalizeOptionalTeamProviderId } from '@shared/utils/teamProvider'; import { AlertTriangle, CheckCircle2, Info, Loader2, X } from 'lucide-react'; @@ -53,22 +56,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 +111,7 @@ function getStoredTeamModel(providerId: TeamProviderId): string { if (stored === null) { return providerId === 'anthropic' ? 'opus' : ''; } - return normalizeTeamModelForUi(providerId, stored === '__default__' ? '' : stored); + return normalizeCatalogTeamModelForUi(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 fb9b0a30..ad9e4fd0 100644 --- a/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx +++ b/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx @@ -47,7 +47,10 @@ import { getTeamModelSelectionError, normalizeTeamModelForUi, } from '@renderer/utils/teamModelAvailability'; -import { getTeamProviderLabel as getCatalogTeamProviderLabel } from '@renderer/utils/teamModelCatalog'; +import { + getTeamProviderLabel as getCatalogTeamProviderLabel, + normalizeTeamModelForUi as normalizeCatalogTeamModelForUi, +} from '@renderer/utils/teamModelCatalog'; import { DEFAULT_PROVIDER_MODEL_SELECTION } from '@shared/utils/providerModelSelection'; import { isTeamProviderId, normalizeOptionalTeamProviderId } from '@shared/utils/teamProvider'; import { @@ -69,22 +72,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, @@ -192,7 +195,7 @@ function getStoredTeamModel(providerId: TeamProviderId): string { if (stored === null) { return providerId === 'anthropic' ? 'opus' : ''; } - return normalizeTeamModelForUi(providerId, stored === '__default__' ? '' : stored); + return normalizeCatalogTeamModelForUi(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..638097b1 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 { normalizeTeamModelForUi as normalizeCatalogTeamModelForUi } from '@renderer/utils/teamModelCatalog'; 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) + ? normalizeCatalogTeamModelForUi(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/MemberCard.tsx b/src/renderer/components/team/members/MemberCard.tsx index bcf7ebc2..3937e09f 100644 --- a/src/renderer/components/team/members/MemberCard.tsx +++ b/src/renderer/components/team/members/MemberCard.tsx @@ -111,8 +111,7 @@ export const MemberCard = ({ !isRemoved && presenceLabel === 'starting' && spawnLaunchState !== 'failed_to_start' && - !activityTask && - !runtimeSummary; + !activityTask; const showStartingBadge = !isRemoved && presenceLabel === 'starting' && !activityTask; const showRuntimeAdvisoryBadge = !isRemoved && 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..599ebd15 100644 --- a/src/renderer/components/team/members/membersEditorUtils.ts +++ b/src/renderer/components/team/members/membersEditorUtils.ts @@ -3,6 +3,8 @@ 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 { normalizeTeamModelForUi as normalizeCatalogTeamModelForUi } from '@renderer/utils/teamModelCatalog'; +import { extractProviderScopedBaseModel } from '@renderer/utils/teamModelContext'; import { isLeadMember } from '@shared/utils/leadDetection'; import { normalizeOptionalTeamProviderId } from '@shared/utils/teamProvider'; @@ -32,6 +34,7 @@ function newDraftId(): string { export function createMemberDraft(initial?: Partial): MemberDraft { const providerId = initial?.providerId; + const normalizedModel = extractProviderScopedBaseModel(initial?.model ?? '', providerId) ?? ''; return { id: initial?.id ?? newDraftId(), name: initial?.name ?? '', @@ -39,7 +42,7 @@ export function createMemberDraft(initial?: Partial): MemberDraft { customRole: initial?.customRole ?? '', workflow: initial?.workflow, providerId, - model: normalizeTeamModelForUi(providerId, initial?.model ?? ''), + model: normalizeCatalogTeamModelForUi(providerId, normalizedModel), effort: initial?.effort, removedAt: initial?.removedAt, }; diff --git a/src/renderer/store/slices/extensionsSlice.ts b/src/renderer/store/slices/extensionsSlice.ts index ca766aaf..0fcb0ac8 100644 --- a/src/renderer/store/slices/extensionsSlice.ts +++ b/src/renderer/store/slices/extensionsSlice.ts @@ -4,14 +4,14 @@ */ import { api } from '@renderer/api'; -import { isProjectScopedMcpScope } from '@shared/utils/mcpScopes'; import { getExtensionActionDisableReason, getMcpDiagnosticKey, - getMcpProjectStateKey, getMcpOperationKey, + getMcpProjectStateKey, getPluginOperationKey, } from '@shared/utils/extensionNormalizers'; +import { isProjectScopedMcpScope } from '@shared/utils/mcpScopes'; import { findPaneByTabId, updatePane } from '../utils/paneHelpers'; diff --git a/src/renderer/store/slices/teamSlice.ts b/src/renderer/store/slices/teamSlice.ts index 8060f2f1..110f2120 100644 --- a/src/renderer/store/slices/teamSlice.ts +++ b/src/renderer/store/slices/teamSlice.ts @@ -94,10 +94,10 @@ type TeamGraphConfigMemberSeedInput = Pick< NonNullable[number], 'name' | 'agentId' | 'removedAt' >; -type TeamGraphLayoutSessionState = { +interface TeamGraphLayoutSessionState { mode: 'default' | 'manual'; signature: string | null; -}; +} export function isTeamDataRefreshPending(teamName: string): boolean { return ( @@ -1025,8 +1025,7 @@ function areTeamGraphSlotAssignmentsEqual( for (const [stableOwnerId, leftAssignment] of leftEntries) { const rightAssignment = right?.[stableOwnerId]; if ( - !rightAssignment || - rightAssignment.ringIndex !== leftAssignment.ringIndex || + rightAssignment?.ringIndex !== leftAssignment.ringIndex || rightAssignment.sectorIndex !== leftAssignment.sectorIndex ) { return false; @@ -2063,8 +2062,7 @@ export const createTeamSlice: StateCreator = (set, return ( (nextAssignment.ringIndex === assignment.ringIndex && nextAssignment.sectorIndex === assignment.sectorIndex) || - (displacedAssignment != null && - nextAssignment.ringIndex === displacedAssignment.ringIndex && + (nextAssignment.ringIndex === displacedAssignment?.ringIndex && nextAssignment.sectorIndex === displacedAssignment.sectorIndex) ); } 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/skillCommandSuggestions.ts b/src/renderer/utils/skillCommandSuggestions.ts index 63bc0b43..62ebbf20 100644 --- a/src/renderer/utils/skillCommandSuggestions.ts +++ b/src/renderer/utils/skillCommandSuggestions.ts @@ -2,8 +2,8 @@ import { getSkillAudienceLabel, isSkillAvailableForProvider } from '@shared/util import { isSupportedSlashCommandName } from '@shared/utils/slashCommands'; import type { MentionSuggestion } from '@renderer/types/mention'; -import type { SkillCatalogItem } from '@shared/types/extensions'; import type { TeamProviderId } from '@shared/types'; +import type { SkillCatalogItem } from '@shared/types/extensions'; import type { KnownSlashCommandDefinition } from '@shared/utils/slashCommands'; function orderSkillsForProvider( diff --git a/src/renderer/utils/teamModelAvailability.ts b/src/renderer/utils/teamModelAvailability.ts index f42d2275..d588a1bc 100644 --- a/src/renderer/utils/teamModelAvailability.ts +++ b/src/renderer/utils/teamModelAvailability.ts @@ -1,3 +1,22 @@ +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 type { CliProviderId, CliProviderModelAvailability, @@ -6,29 +25,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, 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/utils/extensionNormalizers.ts b/src/shared/utils/extensionNormalizers.ts index e3da4d7d..484c441d 100644 --- a/src/shared/utils/extensionNormalizers.ts +++ b/src/shared/utils/extensionNormalizers.ts @@ -9,9 +9,9 @@ import { import type { CliInstallationStatus, - InstallScope, InstalledMcpEntry, InstalledPluginEntry, + InstallScope, PluginCapability, PluginCatalogItem, } from '@shared/types'; @@ -206,7 +206,7 @@ export function getPreferredMcpInstallationEntry( return [...installations].sort( (left, right) => MCP_SCOPE_PRIORITY[left.scope] - MCP_SCOPE_PRIORITY[right.scope] - )[0]!; + )[0]; } /** diff --git a/test/main/services/infrastructure/CliInstallerService.test.ts b/test/main/services/infrastructure/CliInstallerService.test.ts index ffad35a7..8e7549c7 100644 --- a/test/main/services/infrastructure/CliInstallerService.test.ts +++ b/test/main/services/infrastructure/CliInstallerService.test.ts @@ -231,7 +231,7 @@ describe('CliInstallerService', () => { verificationState: 'verified', modelVerificationState: 'idle', statusMessage: null, - models: ['gpt-5.4', 'gpt-5.2-codex'], + models: ['gpt-5.4', 'gpt-5.4-mini'], modelAvailability: [], canLoginFromUi: true, capabilities: { teamLaunch: true, oneShot: true }, @@ -267,14 +267,12 @@ describe('CliInstallerService', () => { if (normalizedArgs === '--version') { return { stdout: '2.3.4', stderr: '' }; } + if (normalizedArgs.includes('--model gpt-5.4-mini')) { + throw new Error("The 'gpt-5.4-mini' model is not supported in this Codex runtime."); + } if (normalizedArgs.includes('--model gpt-5.4')) { return { stdout: 'PONG', stderr: '' }; } - if (normalizedArgs.includes('--model gpt-5.2-codex')) { - throw new Error( - "The 'gpt-5.2-codex' model is not supported when using Codex with a ChatGPT account." - ); - } throw new Error(`Unexpected execCli call: ${normalizedArgs}`); }); @@ -291,7 +289,7 @@ describe('CliInstallerService', () => { expect(verifiedProvider?.modelAvailability).toEqual( expect.arrayContaining([ expect.objectContaining({ modelId: 'gpt-5.4', status: 'checking' }), - expect.objectContaining({ modelId: 'gpt-5.2-codex', status: 'checking' }), + expect.objectContaining({ modelId: 'gpt-5.4-mini', status: 'checking' }), ]) ); @@ -303,7 +301,7 @@ describe('CliInstallerService', () => { expect(latestCodexProvider?.modelAvailability).toEqual([ expect.objectContaining({ modelId: 'gpt-5.4', status: 'available' }), expect.objectContaining({ - modelId: 'gpt-5.2-codex', + modelId: 'gpt-5.4-mini', status: 'unavailable', }), ]); diff --git a/test/renderer/components/extensions/mcp/McpServersPanel.test.ts b/test/renderer/components/extensions/mcp/McpServersPanel.test.ts index 34997fc9..2b988637 100644 --- a/test/renderer/components/extensions/mcp/McpServersPanel.test.ts +++ b/test/renderer/components/extensions/mcp/McpServersPanel.test.ts @@ -40,7 +40,7 @@ interface StoreState { installed?: boolean; binaryPath?: string | null; launchError?: string | null; - }; + } | null; } const storeState = {} as StoreState; diff --git a/test/renderer/components/extensions/skills/SkillsPanel.test.ts b/test/renderer/components/extensions/skills/SkillsPanel.test.ts index ced38887..87579c28 100644 --- a/test/renderer/components/extensions/skills/SkillsPanel.test.ts +++ b/test/renderer/components/extensions/skills/SkillsPanel.test.ts @@ -4,6 +4,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import type { CliInstallationStatus } from '@shared/types'; import type { SkillCatalogItem } from '@shared/types/extensions'; +import { createDefaultCliExtensionCapabilities } from '@shared/utils/providerExtensionCapabilities'; interface StoreState { fetchSkillsCatalog: ReturnType; @@ -213,12 +214,9 @@ function makeMultimodelStatus( capabilities: { teamLaunch: true, oneShot: true, - extensions: { - plugins: { status: 'supported', ownership: 'provider', reason: null }, - mcp: { status: 'supported', ownership: 'shared', reason: null }, - skills: { status: 'supported', ownership: 'shared', reason: null }, - apiKeys: { status: 'supported', ownership: 'shared', reason: null }, - }, + extensions: createDefaultCliExtensionCapabilities({ + plugins: { status: 'supported', ownership: 'provider-scoped', reason: null }, + }), }, connection: null, backend: null, @@ -405,12 +403,9 @@ describe('SkillsPanel', () => { capabilities: { teamLaunch: true, oneShot: true, - extensions: { - plugins: { status: 'unsupported', ownership: 'provider', reason: null }, - mcp: { status: 'supported', ownership: 'shared', reason: null }, - skills: { status: 'supported', ownership: 'shared', reason: null }, - apiKeys: { status: 'supported', ownership: 'shared', reason: null }, - }, + extensions: createDefaultCliExtensionCapabilities({ + plugins: { status: 'unsupported', ownership: 'provider-scoped', reason: null }, + }), }, connection: null, backend: null, diff --git a/test/renderer/store/extensionsSlice.test.ts b/test/renderer/store/extensionsSlice.test.ts index e994b3b9..25fa2d96 100644 --- a/test/renderer/store/extensionsSlice.test.ts +++ b/test/renderer/store/extensionsSlice.test.ts @@ -55,12 +55,14 @@ vi.mock('../../../src/renderer/api', () => ({ })); import { api } from '../../../src/renderer/api'; +import type { CliInstallationStatus } from '../../../src/shared/types'; import { getMcpDiagnosticKey, getMcpProjectStateKey, getMcpOperationKey, getPluginOperationKey, } from '../../../src/shared/utils/extensionNormalizers'; +import { createDefaultCliExtensionCapabilities } from '../../../src/shared/utils/providerExtensionCapabilities'; import type { EnrichedPlugin, @@ -137,7 +139,7 @@ const makeSkillDetail = (overrides: Partial = {}): SkillDetail => ( ...overrides, }); -const makeReadyCliStatus = () => ({ +const makeReadyCliStatus = (): CliInstallationStatus => ({ flavor: 'claude' as const, displayName: 'Claude', supportsSelfUpdate: true, @@ -154,7 +156,10 @@ const makeReadyCliStatus = () => ({ providers: [], }); -const makeLimitedMultimodelCliStatus = (section: 'plugins' | 'mcp', reason: string) => ({ +const makeLimitedMultimodelCliStatus = ( + section: 'plugins' | 'mcp', + reason: string +): CliInstallationStatus => ({ flavor: 'agent_teams_orchestrator' as const, displayName: 'Claude Multimodel', supportsSelfUpdate: false, @@ -181,21 +186,22 @@ const makeLimitedMultimodelCliStatus = (section: 'plugins' | 'mcp', reason: stri capabilities: { teamLaunch: true, oneShot: true, - extensions: { + extensions: createDefaultCliExtensionCapabilities({ plugins: { status: section === 'plugins' ? 'unsupported' : 'supported', - ownership: 'shared' as const, + ownership: 'shared', reason: section === 'plugins' ? reason : null, }, mcp: { status: section === 'mcp' ? 'read-only' : 'supported', - ownership: 'shared' as const, + ownership: 'shared', reason: section === 'mcp' ? reason : null, }, - skills: { status: 'supported', ownership: 'shared' as const, reason: null }, - apiKeys: { status: 'supported', ownership: 'shared' as const, reason: null }, - }, + }), }, + statusMessage: null, + connection: null, + backend: null, }, ], }); diff --git a/test/renderer/utils/multimodelProviderVisibility.test.ts b/test/renderer/utils/multimodelProviderVisibility.test.ts index 84ab2872..4ee98ca3 100644 --- a/test/renderer/utils/multimodelProviderVisibility.test.ts +++ b/test/renderer/utils/multimodelProviderVisibility.test.ts @@ -7,6 +7,7 @@ import { } from '@renderer/utils/multimodelProviderVisibility'; import type { CliInstallationStatus, CliProviderStatus } from '@shared/types'; +import { createDefaultCliExtensionCapabilities } from '@shared/utils/providerExtensionCapabilities'; function createProvider(providerId: CliProviderStatus['providerId']): CliProviderStatus { return { @@ -21,9 +22,9 @@ function createProvider(providerId: CliProviderStatus['providerId']): CliProvide capabilities: { teamLaunch: true, oneShot: true, + extensions: createDefaultCliExtensionCapabilities(), }, statusMessage: null, - detailMessage: null, selectedBackendId: null, resolvedBackendId: null, availableBackends: [],