fix(ci): restore workspace checks

This commit is contained in:
777genius 2026-04-17 22:45:19 +03:00
parent c70ebfe9a5
commit 18d9f2b4a4
48 changed files with 533 additions and 201 deletions

View file

@ -311,7 +311,8 @@
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"tool_use_system_prompt_tokens": 346, "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": { "global.anthropic.claude-opus-4-6-v1": {
"cache_creation_input_token_cost": 0.00000625, "cache_creation_input_token_cost": 0.00000625,
@ -338,7 +339,8 @@
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"tool_use_system_prompt_tokens": 346, "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": { "us.anthropic.claude-opus-4-6-v1": {
"cache_creation_input_token_cost": 0.000006875, "cache_creation_input_token_cost": 0.000006875,
@ -365,7 +367,8 @@
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"tool_use_system_prompt_tokens": 346, "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": { "eu.anthropic.claude-opus-4-6-v1": {
"cache_creation_input_token_cost": 0.000006875, "cache_creation_input_token_cost": 0.000006875,
@ -392,7 +395,8 @@
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"tool_use_system_prompt_tokens": 346, "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": { "au.anthropic.claude-opus-4-6-v1": {
"cache_creation_input_token_cost": 0.000006875, "cache_creation_input_token_cost": 0.000006875,
@ -419,6 +423,147 @@
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"tool_use_system_prompt_tokens": 346, "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 "supports_native_structured_output": true
}, },
"anthropic.claude-sonnet-4-6": { "anthropic.claude-sonnet-4-6": {
@ -854,6 +999,35 @@
"supports_response_schema": true, "supports_response_schema": true,
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": 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 "tool_use_system_prompt_tokens": 159
}, },
"azure_ai/claude-opus-4-1": { "azure_ai/claude-opus-4-1": {
@ -1687,7 +1861,8 @@
"provider_specific_entry": { "provider_specific_entry": {
"us": 1.1, "us": 1.1,
"fast": 6 "fast": 6
} },
"supports_max_reasoning_effort": true
}, },
"claude-opus-4-6-20260205": { "claude-opus-4-6-20260205": {
"cache_creation_input_token_cost": 0.00000625, "cache_creation_input_token_cost": 0.00000625,
@ -1715,6 +1890,71 @@
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"tool_use_system_prompt_tokens": 346, "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": { "provider_specific_entry": {
"us": 1.1, "us": 1.1,
"fast": 6 "fast": 6
@ -4148,7 +4388,8 @@
"supports_response_schema": true, "supports_response_schema": true,
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": 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": { "vertex_ai/claude-opus-4-6@default": {
"cache_creation_input_token_cost": 0.00000625, "cache_creation_input_token_cost": 0.00000625,
@ -4174,6 +4415,61 @@
"supports_response_schema": true, "supports_response_schema": true,
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": 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 "tool_use_system_prompt_tokens": 346
}, },
"vertex_ai/claude-sonnet-4-5": { "vertex_ai/claude-sonnet-4-5": {

View file

@ -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]);
}

View file

@ -3,16 +3,15 @@
* Follows the exact ProjectEditorOverlay pattern (lazy-loaded, fixed z-50). * 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 { GraphView } from '@claude-teams/agent-graph';
import { TeamSidebarHost } from '@renderer/components/team/sidebar/TeamSidebarHost'; 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 { useGraphCreateTaskDialog } from '../hooks/useGraphCreateTaskDialog';
import { useGraphSidebarVisibility } from '../hooks/useGraphSidebarVisibility'; import { useGraphSidebarVisibility } from '../hooks/useGraphSidebarVisibility';
import { useTeamGraphAdapter } from '../hooks/useTeamGraphAdapter'; import { useTeamGraphAdapter } from '../hooks/useTeamGraphAdapter';
import { useTeamGraphSlotReset } from '../hooks/useTeamGraphSlotReset';
import { useTeamGraphSurfaceActions } from '../hooks/useTeamGraphSurfaceActions'; import { useTeamGraphSurfaceActions } from '../hooks/useTeamGraphSurfaceActions';
import { GraphActivityHud } from './GraphActivityHud'; import { GraphActivityHud } from './GraphActivityHud';
@ -55,15 +54,14 @@ export const TeamGraphOverlay = ({
}: TeamGraphOverlayProps): React.JSX.Element => { }: TeamGraphOverlayProps): React.JSX.Element => {
const graphData = useTeamGraphAdapter(teamName); const graphData = useTeamGraphAdapter(teamName);
const { openTeamPage: openTeamTab, commitOwnerSlotDrop } = useTeamGraphSurfaceActions(teamName); const { openTeamPage: openTeamTab, commitOwnerSlotDrop } = useTeamGraphSurfaceActions(teamName);
const resetTeamGraphSlotAssignmentsToDefaults = useStore(
(s) => s.resetTeamGraphSlotAssignmentsToDefaults
);
const { sidebarVisible: persistedSidebarVisible, toggleSidebarVisible } = const { sidebarVisible: persistedSidebarVisible, toggleSidebarVisible } =
useGraphSidebarVisibility(); useGraphSidebarVisibility();
const { dialog: createTaskDialog, openCreateTaskDialog } = useGraphCreateTaskDialog(teamName); const { dialog: createTaskDialog, openCreateTaskDialog } = useGraphCreateTaskDialog(teamName);
const effectiveSidebarVisible = sidebarVisible ?? persistedSidebarVisible; const effectiveSidebarVisible = sidebarVisible ?? persistedSidebarVisible;
const handleToggleSidebar = onToggleSidebar ?? toggleSidebarVisible; const handleToggleSidebar = onToggleSidebar ?? toggleSidebarVisible;
useTeamGraphSlotReset(teamName);
// Task action dispatchers (same pattern as TeamGraphTab) // Task action dispatchers (same pattern as TeamGraphTab)
const dispatchTaskAction = useCallback( const dispatchTaskAction = useCallback(
(action: string) => (taskId: string) => (action: string) => (taskId: string) =>
@ -91,13 +89,6 @@ export const TeamGraphOverlay = ({
openCreateTaskDialog(''); openCreateTaskDialog('');
}, [openCreateTaskDialog]); }, [openCreateTaskDialog]);
useLayoutEffect(() => {
if (!isTeamGraphSlotPersistenceDisabled()) {
return;
}
resetTeamGraphSlotAssignmentsToDefaults(teamName);
}, [resetTeamGraphSlotAssignmentsToDefaults, teamName]);
const events: GraphEventPort = { const events: GraphEventPort = {
onNodeDoubleClick: useCallback( onNodeDoubleClick: useCallback(
(ref: GraphDomainRef) => { (ref: GraphDomainRef) => {

View file

@ -3,16 +3,15 @@
* Provides Fullscreen button that opens the overlay. * 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 { GraphView } from '@claude-teams/agent-graph';
import { TeamSidebarHost } from '@renderer/components/team/sidebar/TeamSidebarHost'; 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 { useGraphCreateTaskDialog } from '../hooks/useGraphCreateTaskDialog';
import { useGraphSidebarVisibility } from '../hooks/useGraphSidebarVisibility'; import { useGraphSidebarVisibility } from '../hooks/useGraphSidebarVisibility';
import { useTeamGraphAdapter } from '../hooks/useTeamGraphAdapter'; import { useTeamGraphAdapter } from '../hooks/useTeamGraphAdapter';
import { useTeamGraphSlotReset } from '../hooks/useTeamGraphSlotReset';
import { useTeamGraphSurfaceActions } from '../hooks/useTeamGraphSurfaceActions'; import { useTeamGraphSurfaceActions } from '../hooks/useTeamGraphSurfaceActions';
import { GraphActivityHud } from './GraphActivityHud'; import { GraphActivityHud } from './GraphActivityHud';
@ -48,13 +47,12 @@ export const TeamGraphTab = ({
}: TeamGraphTabProps): React.JSX.Element => { }: TeamGraphTabProps): React.JSX.Element => {
const graphData = useTeamGraphAdapter(teamName); const graphData = useTeamGraphAdapter(teamName);
const { openTeamPage, commitOwnerSlotDrop } = useTeamGraphSurfaceActions(teamName); const { openTeamPage, commitOwnerSlotDrop } = useTeamGraphSurfaceActions(teamName);
const resetTeamGraphSlotAssignmentsToDefaults = useStore(
(s) => s.resetTeamGraphSlotAssignmentsToDefaults
);
const [fullscreen, setFullscreen] = useState(false); const [fullscreen, setFullscreen] = useState(false);
const { sidebarVisible, toggleSidebarVisible } = useGraphSidebarVisibility(); const { sidebarVisible, toggleSidebarVisible } = useGraphSidebarVisibility();
const { dialog: createTaskDialog, openCreateTaskDialog } = useGraphCreateTaskDialog(teamName); const { dialog: createTaskDialog, openCreateTaskDialog } = useGraphCreateTaskDialog(teamName);
useTeamGraphSlotReset(teamName, isActive);
// Typed event dispatchers (DRY — used in both events + renderOverlay) // Typed event dispatchers (DRY — used in both events + renderOverlay)
const dispatchOpenTask = useCallback( const dispatchOpenTask = useCallback(
(taskId: string) => (taskId: string) =>
@ -81,13 +79,6 @@ export const TeamGraphTab = ({
openCreateTaskDialog(''); openCreateTaskDialog('');
}, [openCreateTaskDialog]); }, [openCreateTaskDialog]);
useLayoutEffect(() => {
if (!isTeamGraphSlotPersistenceDisabled() || !isActive) {
return;
}
resetTeamGraphSlotAssignmentsToDefaults(teamName);
}, [isActive, resetTeamGraphSlotAssignmentsToDefaults, teamName]);
// Task action dispatchers // Task action dispatchers
const dispatchTaskAction = useCallback( const dispatchTaskAction = useCallback(
(action: string) => (taskId: string) => (action: string) => (taskId: string) =>

View file

@ -3,6 +3,7 @@ import type { DashboardRecentProject, DashboardRecentProjectsPayload } from './d
export type DashboardRecentProjectsPayloadLike = export type DashboardRecentProjectsPayloadLike =
| DashboardRecentProjectsPayload | DashboardRecentProjectsPayload
| DashboardRecentProject[] | DashboardRecentProject[]
| { degraded?: unknown; projects?: unknown }
| null | null
| undefined; | undefined;

View file

@ -1,7 +1,7 @@
import { import {
DASHBOARD_RECENT_PROJECTS_ROUTE, DASHBOARD_RECENT_PROJECTS_ROUTE,
normalizeDashboardRecentProjectsPayload,
type DashboardRecentProjectsPayload, type DashboardRecentProjectsPayload,
normalizeDashboardRecentProjectsPayload,
} from '@features/recent-projects/contracts'; } from '@features/recent-projects/contracts';
import { createLogger } from '@shared/utils/logger'; import { createLogger } from '@shared/utils/logger';

View file

@ -1,3 +1,8 @@
import {
type DashboardRecentProjectsPayload,
normalizeDashboardRecentProjectsPayload,
} from '@features/recent-projects/contracts';
import { ListDashboardRecentProjectsUseCase } from '../../core/application/use-cases/ListDashboardRecentProjectsUseCase'; import { ListDashboardRecentProjectsUseCase } from '../../core/application/use-cases/ListDashboardRecentProjectsUseCase';
import { DashboardRecentProjectsPresenter } from '../adapters/output/presenters/DashboardRecentProjectsPresenter'; import { DashboardRecentProjectsPresenter } from '../adapters/output/presenters/DashboardRecentProjectsPresenter';
import { ClaudeRecentProjectsSourceAdapter } from '../adapters/output/sources/ClaudeRecentProjectsSourceAdapter'; 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 { ClockPort } from '../../core/application/ports/ClockPort';
import type { LoggerPort } from '../../core/application/ports/LoggerPort'; import type { LoggerPort } from '../../core/application/ports/LoggerPort';
import {
normalizeDashboardRecentProjectsPayload,
type DashboardRecentProjectsPayload,
} from '@features/recent-projects/contracts';
import type { ServiceContext } from '@main/services'; import type { ServiceContext } from '@main/services';
export interface RecentProjectsFeatureFacade { export interface RecentProjectsFeatureFacade {

View file

@ -1,9 +1,10 @@
import type {
DashboardRecentProjectsPayloadLike,
DashboardRecentProjectsPayload,
} from '@features/recent-projects/contracts';
import { normalizeDashboardRecentProjectsPayload } 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_CACHE_TTL_MS = 15_000;
const RECENT_PROJECTS_CLIENT_DEGRADED_CACHE_TTL_MS = 1_500; const RECENT_PROJECTS_CLIENT_DEGRADED_CACHE_TTL_MS = 1_500;

View file

@ -70,6 +70,7 @@ import { setReviewMainWindow } from './ipc/review';
import { setTmuxMainWindow } from './ipc/tmux'; import { setTmuxMainWindow } from './ipc/tmux';
import { import {
ApiKeyService, ApiKeyService,
createExtensionsRuntimeAdapter,
ExtensionFacadeService, ExtensionFacadeService,
GlamaMcpEnrichmentService, GlamaMcpEnrichmentService,
McpCatalogAggregator, McpCatalogAggregator,
@ -84,7 +85,6 @@ import {
SkillsCatalogService, SkillsCatalogService,
SkillsMutationService, SkillsMutationService,
SkillsWatcherService, SkillsWatcherService,
createExtensionsRuntimeAdapter,
} from './services/extensions'; } from './services/extensions';
import { startEventLoopLagMonitor } from './services/infrastructure/EventLoopLagMonitor'; import { startEventLoopLagMonitor } from './services/infrastructure/EventLoopLagMonitor';
import { HttpServer } from './services/infrastructure/HttpServer'; import { HttpServer } from './services/infrastructure/HttpServer';

View file

@ -11,6 +11,11 @@ export { PluginCatalogService } from './catalog/PluginCatalogService';
export { ExtensionFacadeService } from './ExtensionFacadeService'; export { ExtensionFacadeService } from './ExtensionFacadeService';
export { McpInstallService } from './install/McpInstallService'; export { McpInstallService } from './install/McpInstallService';
export { PluginInstallService } from './install/PluginInstallService'; export { PluginInstallService } from './install/PluginInstallService';
export {
ClaudeExtensionsAdapter,
createExtensionsRuntimeAdapter,
MultimodelExtensionsAdapter,
} from './runtime/ExtensionsRuntimeAdapter';
export { SkillImportService } from './skills/SkillImportService'; export { SkillImportService } from './skills/SkillImportService';
export { SkillMetadataParser } from './skills/SkillMetadataParser'; export { SkillMetadataParser } from './skills/SkillMetadataParser';
export { SkillPlanService } from './skills/SkillPlanService'; export { SkillPlanService } from './skills/SkillPlanService';
@ -22,11 +27,6 @@ export { SkillsCatalogService } from './skills/SkillsCatalogService';
export { SkillsMutationService } from './skills/SkillsMutationService'; export { SkillsMutationService } from './skills/SkillsMutationService';
export { SkillsWatcherService } from './skills/SkillsWatcherService'; export { SkillsWatcherService } from './skills/SkillsWatcherService';
export { SkillValidator } from './skills/SkillValidator'; export { SkillValidator } from './skills/SkillValidator';
export {
ClaudeExtensionsAdapter,
createExtensionsRuntimeAdapter,
MultimodelExtensionsAdapter,
} from './runtime/ExtensionsRuntimeAdapter';
export { McpHealthDiagnosticsService } from './state/McpHealthDiagnosticsService'; export { McpHealthDiagnosticsService } from './state/McpHealthDiagnosticsService';
export { McpInstallationStateService } from './state/McpInstallationStateService'; export { McpInstallationStateService } from './state/McpInstallationStateService';
export { PluginInstallationStateService } from './state/PluginInstallationStateService'; export { PluginInstallationStateService } from './state/PluginInstallationStateService';

View file

@ -1,7 +1,7 @@
import { buildProviderAwareCliEnv } from '@main/services/runtime/providerAwareCliEnv';
import { ClaudeBinaryResolver } from '@main/services/team/ClaudeBinaryResolver'; import { ClaudeBinaryResolver } from '@main/services/team/ClaudeBinaryResolver';
import { getConfiguredCliFlavor } from '@main/services/team/cliFlavor'; import { getConfiguredCliFlavor } from '@main/services/team/cliFlavor';
import { execCli } from '@main/utils/childProcess'; import { execCli } from '@main/utils/childProcess';
import { buildProviderAwareCliEnv } from '@main/services/runtime/providerAwareCliEnv';
import { CLI_NOT_FOUND_MESSAGE } from '@shared/constants/cli'; import { CLI_NOT_FOUND_MESSAGE } from '@shared/constants/cli';
import { McpConfigStateReader } from './McpConfigStateReader'; 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_LIST_TIMEOUT_MS = 15_000;
const MCP_DIAGNOSE_TIMEOUT_MS = 60_000; const MCP_DIAGNOSE_TIMEOUT_MS = 60_000;
async function buildManagementCliEnvForBinary(binaryPath: string): Promise<NodeJS.ProcessEnv> {
const { env } = await buildProviderAwareCliEnv({
binaryPath,
connectionMode: 'augment',
});
return env;
}
export interface ExtensionsRuntimeAdapter { export interface ExtensionsRuntimeAdapter {
readonly flavor: CliFlavor; readonly flavor: CliFlavor;
buildManagementCliEnv(binaryPath: string): Promise<NodeJS.ProcessEnv>; buildManagementCliEnv(binaryPath: string): Promise<NodeJS.ProcessEnv>;
@ -27,11 +35,7 @@ export class ClaudeExtensionsAdapter implements ExtensionsRuntimeAdapter {
constructor(private readonly stateReader = new McpConfigStateReader()) {} constructor(private readonly stateReader = new McpConfigStateReader()) {}
async buildManagementCliEnv(binaryPath: string): Promise<NodeJS.ProcessEnv> { async buildManagementCliEnv(binaryPath: string): Promise<NodeJS.ProcessEnv> {
const { env } = await buildProviderAwareCliEnv({ return buildManagementCliEnvForBinary(binaryPath);
binaryPath,
connectionMode: 'augment',
});
return env;
} }
async getInstalledMcp(projectPath?: string): Promise<InstalledMcpEntry[]> { async getInstalledMcp(projectPath?: string): Promise<InstalledMcpEntry[]> {
@ -59,11 +63,7 @@ export class MultimodelExtensionsAdapter implements ExtensionsRuntimeAdapter {
readonly flavor = 'agent_teams_orchestrator' as const; readonly flavor = 'agent_teams_orchestrator' as const;
async buildManagementCliEnv(binaryPath: string): Promise<NodeJS.ProcessEnv> { async buildManagementCliEnv(binaryPath: string): Promise<NodeJS.ProcessEnv> {
const { env } = await buildProviderAwareCliEnv({ return buildManagementCliEnvForBinary(binaryPath);
binaryPath,
connectionMode: 'augment',
});
return env;
} }
async getInstalledMcp(projectPath?: string): Promise<InstalledMcpEntry[]> { async getInstalledMcp(projectPath?: string): Promise<InstalledMcpEntry[]> {

View file

@ -17,8 +17,17 @@ interface McpDiagnoseJsonPayload {
} }
const EMBEDDED_HTTP_URL_PATTERN = /https?:\/\/[^\s"'`]+/gi; const EMBEDDED_HTTP_URL_PATTERN = /https?:\/\/[^\s"'`]+/gi;
const SENSITIVE_FLAG_VALUE_PATTERN = const SENSITIVE_FLAG_VALUE_PATTERN = /(--[a-z0-9_-]+)(?:=([^\s]+)|\s+([^\s]+))/gi;
/(--(?:api[-_]?key|access[-_]?token|auth[-_]?token|token|secret|password|client[-_]?secret))(?:=([^\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 { function isPluginInjectedDiagnosticName(name: string): boolean {
return name.startsWith('plugin:'); return name.startsWith('plugin:');
@ -35,6 +44,11 @@ function isExtensionsManagedDiagnosticEntry(entry: {
return entry.scope === undefined || isInstalledMcpScope(entry.scope); 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<T>(raw: string): T { function extractJsonObject<T>(raw: string): T {
const trimmed = raw.trim(); const trimmed = raw.trim();
try { try {
@ -75,22 +89,27 @@ function redactHttpUrl(urlString: string): string {
return urlString; 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; return urlString;
} }
if (parsed.username) parsed.username = '***'; const redactedSearchParams = new URLSearchParams(parsed.search);
if (parsed.password) parsed.password = '***'; for (const key of new Set(redactedSearchParams.keys())) {
redactedSearchParams.set(key, 'REDACTED');
for (const key of new Set(parsed.searchParams.keys())) {
parsed.searchParams.set(key, 'REDACTED');
} }
if (parsed.hash) { const authPrefix =
parsed.hash = 'REDACTED'; 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 { } catch {
return urlString; return urlString;
} }
@ -99,8 +118,15 @@ function redactHttpUrl(urlString: string): string {
function redactDiagnosticTarget(target: string): string { function redactDiagnosticTarget(target: string): string {
return target return target
.replace(EMBEDDED_HTTP_URL_PATTERN, (match) => redactHttpUrl(match)) .replace(EMBEDDED_HTTP_URL_PATTERN, (match) => redactHttpUrl(match))
.replace(SENSITIVE_FLAG_VALUE_PATTERN, (_match, flag: string, inlineValue?: string) => .replace(
inlineValue ? `${flag}=REDACTED` : `${flag} REDACTED` SENSITIVE_FLAG_VALUE_PATTERN,
(match, flag: string, inlineValue?: string, separatedValue?: string) => {
if (!isSensitiveCliFlag(flag)) {
return match;
}
return inlineValue || separatedValue ? `${flag}=REDACTED` : `${flag} REDACTED`;
}
); );
} }

View file

@ -51,8 +51,8 @@ import type {
CliInstallationStatus, CliInstallationStatus,
CliInstallerProgress, CliInstallerProgress,
CliPlatform, CliPlatform,
CliProviderModelAvailability,
CliProviderId, CliProviderId,
CliProviderModelAvailability,
CliProviderStatus, CliProviderStatus,
} from '@shared/types'; } from '@shared/types';
import type { BrowserWindow } from 'electron'; import type { BrowserWindow } from 'electron';
@ -610,7 +610,7 @@ export class CliInstallerService {
private updateLatestProviderStatus(providerStatus: CliProviderStatus): void { private updateLatestProviderStatus(providerStatus: CliProviderStatus): void {
if ( if (
providerStatus.modelVerificationState !== 'verifying' && providerStatus.modelVerificationState !== 'verifying' &&
!((providerStatus.modelAvailability?.length ?? 0) > 0) (providerStatus.modelAvailability?.length ?? 0) <= 0
) { ) {
this.latestProviderSignatures.set(providerStatus.providerId, null); this.latestProviderSignatures.set(providerStatus.providerId, null);
} }

View file

@ -33,6 +33,7 @@ import {
import { getMemberColorByName } from '@shared/constants/memberColors'; import { getMemberColorByName } from '@shared/constants/memberColors';
import { DEFAULT_TOOL_APPROVAL_SETTINGS } from '@shared/types/team'; import { DEFAULT_TOOL_APPROVAL_SETTINGS } from '@shared/types/team';
import { resolveLanguageName } from '@shared/utils/agentLanguage'; import { resolveLanguageName } from '@shared/utils/agentLanguage';
import { getAnthropicDefaultTeamModel } from '@shared/utils/anthropicModelDefaults';
import { parseCliArgs } from '@shared/utils/cliArgsParser'; import { parseCliArgs } from '@shared/utils/cliArgsParser';
import { import {
isInboxNoiseMessage, isInboxNoiseMessage,
@ -42,14 +43,13 @@ import {
} from '@shared/utils/inboxNoise'; } from '@shared/utils/inboxNoise';
import { isLeadAgentType, isLeadMember } from '@shared/utils/leadDetection'; import { isLeadAgentType, isLeadMember } from '@shared/utils/leadDetection';
import { createLogger } from '@shared/utils/logger'; 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 { formatTaskDisplayLabel } from '@shared/utils/taskIdentity';
import { import {
parseAllTeammateMessages, parseAllTeammateMessages,
type ParsedTeammateContent, type ParsedTeammateContent,
} from '@shared/utils/teammateMessageParser'; } from '@shared/utils/teammateMessageParser';
import { createCliAutoSuffixNameGuard, parseNumericSuffixName } from '@shared/utils/teamMemberName'; import { createCliAutoSuffixNameGuard, parseNumericSuffixName } from '@shared/utils/teamMemberName';
import { isDefaultProviderModelSelection } from '@shared/utils/providerModelSelection';
import { normalizeOptionalTeamProviderId } from '@shared/utils/teamProvider'; import { normalizeOptionalTeamProviderId } from '@shared/utils/teamProvider';
import { import {
extractToolPreview, extractToolPreview,
@ -68,16 +68,16 @@ import {
type GeminiRuntimeAuthState, type GeminiRuntimeAuthState,
resolveGeminiRuntimeAuth, resolveGeminiRuntimeAuth,
} from '../runtime/geminiRuntimeAuth'; } from '../runtime/geminiRuntimeAuth';
import { buildProviderAwareCliEnv } from '../runtime/providerAwareCliEnv';
import { import {
buildProviderPreflightPingArgs,
buildProviderModelProbeArgs, buildProviderModelProbeArgs,
buildProviderPreflightPingArgs,
classifyProviderModelProbeFailure, classifyProviderModelProbeFailure,
getProviderModelProbeExpectedOutput, getProviderModelProbeExpectedOutput,
getProviderModelProbeTimeoutMs, getProviderModelProbeTimeoutMs,
isProviderModelProbeSuccessOutput, isProviderModelProbeSuccessOutput,
normalizeProviderModelProbeFailureReason, normalizeProviderModelProbeFailureReason,
} from '../runtime/providerModelProbe'; } from '../runtime/providerModelProbe';
import { buildProviderAwareCliEnv } from '../runtime/providerAwareCliEnv';
import { resolveTeamProviderId } from '../runtime/providerRuntimeEnv'; import { resolveTeamProviderId } from '../runtime/providerRuntimeEnv';
import { buildActionModeProtocol } from './actionModeInstructions'; import { buildActionModeProtocol } from './actionModeInstructions';

View file

@ -145,7 +145,7 @@ export class TeammateToolTracker {
const state = this.stateByTeam.get(teamName); const state = this.stateByTeam.get(teamName);
if (!state?.enabled || state.epoch !== expectedEpoch) return; 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); const currentState = this.stateByTeam.get(teamName);
if (!currentState?.enabled || currentState.epoch !== expectedEpoch) return; if (!currentState?.enabled || currentState.epoch !== expectedEpoch) return;

View file

@ -12,7 +12,6 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
import { api, isElectronMode } from '@renderer/api'; import { api, isElectronMode } from '@renderer/api';
import { confirm } from '@renderer/components/common/ConfirmDialog'; import { confirm } from '@renderer/components/common/ConfirmDialog';
import { ProviderBrandLogo } from '@renderer/components/common/ProviderBrandLogo'; import { ProviderBrandLogo } from '@renderer/components/common/ProviderBrandLogo';
import { ProviderModelBadges } from '@renderer/components/runtime/ProviderModelBadges';
import { import {
formatProviderStatusText, formatProviderStatusText,
getProviderConnectionModeSummary, getProviderConnectionModeSummary,
@ -23,6 +22,7 @@ import {
isConnectionManagedRuntimeProvider, isConnectionManagedRuntimeProvider,
shouldShowProviderConnectAction, shouldShowProviderConnectAction,
} from '@renderer/components/runtime/providerConnectionUi'; } from '@renderer/components/runtime/providerConnectionUi';
import { ProviderModelBadges } from '@renderer/components/runtime/ProviderModelBadges';
import { getProviderRuntimeBackendSummary } from '@renderer/components/runtime/ProviderRuntimeBackendSelector'; import { getProviderRuntimeBackendSummary } from '@renderer/components/runtime/ProviderRuntimeBackendSelector';
import { ProviderRuntimeSettingsDialog } from '@renderer/components/runtime/ProviderRuntimeSettingsDialog'; import { ProviderRuntimeSettingsDialog } from '@renderer/components/runtime/ProviderRuntimeSettingsDialog';
import { SettingsToggle } from '@renderer/components/settings/components'; import { SettingsToggle } from '@renderer/components/settings/components';

View file

@ -11,12 +11,12 @@ import { Button } from '@renderer/components/ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip'; import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip';
import { useStore } from '@renderer/store'; import { useStore } from '@renderer/store';
import { formatCompactNumber, formatRelativeTime } from '@renderer/utils/formatters'; import { formatCompactNumber, formatRelativeTime } from '@renderer/utils/formatters';
import { getDefaultMcpSharedScope } from '@shared/utils/mcpScopes';
import { import {
getMcpInstallationSummaryLabel, getMcpInstallationSummaryLabel,
getMcpOperationKey, getMcpOperationKey,
sanitizeMcpServerName, sanitizeMcpServerName,
} from '@shared/utils/extensionNormalizers'; } from '@shared/utils/extensionNormalizers';
import { getDefaultMcpSharedScope } from '@shared/utils/mcpScopes';
import { Clock, Cloud, Globe, KeyRound, Lock, Monitor, Star, Tag, Wrench } from 'lucide-react'; import { Clock, Cloud, Globe, KeyRound, Lock, Monitor, Star, Tag, Wrench } from 'lucide-react';
import { Github as GithubIcon } from 'lucide-react'; import { Github as GithubIcon } from 'lucide-react';

View file

@ -25,18 +25,18 @@ import {
SelectValue, SelectValue,
} from '@renderer/components/ui/select'; } from '@renderer/components/ui/select';
import { useStore } from '@renderer/store'; import { useStore } from '@renderer/store';
import {
getDefaultMcpSharedScope,
getMcpScopeLabel,
isProjectScopedMcpScope,
isSharedMcpScope,
} from '@shared/utils/mcpScopes';
import { import {
getMcpInstallationSummaryLabel, getMcpInstallationSummaryLabel,
getMcpOperationKey, getMcpOperationKey,
getPreferredMcpInstallationEntry, getPreferredMcpInstallationEntry,
sanitizeMcpServerName, sanitizeMcpServerName,
} from '@shared/utils/extensionNormalizers'; } from '@shared/utils/extensionNormalizers';
import {
getDefaultMcpSharedScope,
getMcpScopeLabel,
isProjectScopedMcpScope,
isSharedMcpScope,
} from '@shared/utils/mcpScopes';
import { ExternalLink, Lock, Plus, Star, Trash2, Wrench } from 'lucide-react'; import { ExternalLink, Lock, Plus, Star, Trash2, Wrench } from 'lucide-react';
import { InstallButton } from '../common/InstallButton'; import { InstallButton } from '../common/InstallButton';

View file

@ -5,8 +5,8 @@
import { Badge } from '@renderer/components/ui/badge'; import { Badge } from '@renderer/components/ui/badge';
import { useStore } from '@renderer/store'; import { useStore } from '@renderer/store';
import { import {
getInstallationSummaryLabel,
getCapabilityLabel, getCapabilityLabel,
getInstallationSummaryLabel,
getPluginOperationKey, getPluginOperationKey,
hasInstallationInScope, hasInstallationInScope,
inferCapabilities, inferCapabilities,

View file

@ -25,8 +25,8 @@ import {
} from '@renderer/components/ui/select'; } from '@renderer/components/ui/select';
import { useStore } from '@renderer/store'; import { useStore } from '@renderer/store';
import { import {
getInstallationSummaryLabel,
getCapabilityLabel, getCapabilityLabel,
getInstallationSummaryLabel,
getPluginOperationKey, getPluginOperationKey,
hasInstallationInScope, hasInstallationInScope,
inferCapabilities, inferCapabilities,

View file

@ -16,8 +16,8 @@ import {
SelectValue, SelectValue,
} from '@renderer/components/ui/select'; } from '@renderer/components/ui/select';
import { useStore } from '@renderer/store'; import { useStore } from '@renderer/store';
import { getCliProviderExtensionCapability } from '@shared/utils/providerExtensionCapabilities';
import { inferCapabilities, normalizeCategory } from '@shared/utils/extensionNormalizers'; import { inferCapabilities, normalizeCategory } from '@shared/utils/extensionNormalizers';
import { getCliProviderExtensionCapability } from '@shared/utils/providerExtensionCapabilities';
import { ArrowUpDown, Filter, Puzzle, Search } from 'lucide-react'; import { ArrowUpDown, Filter, Puzzle, Search } from 'lucide-react';
import { useShallow } from 'zustand/react/shallow'; import { useShallow } from 'zustand/react/shallow';

View file

@ -29,6 +29,8 @@ import { useShallow } from 'zustand/react/shallow';
import { resolveSkillProjectPath } from './skillProjectUtils'; import { resolveSkillProjectPath } from './skillProjectUtils';
import type { SkillValidationIssue } from '@shared/types';
interface SkillDetailDialogProps { interface SkillDetailDialogProps {
skillId: string | null; skillId: string | null;
open: boolean; open: boolean;
@ -93,7 +95,7 @@ export const SkillDetailDialog = ({
: 'Runs automatically when it matches the task.'; : 'Runs automatically when it matches the task.';
} }
function getIssuesTone(issues: typeof item.issues): { function getIssuesTone(issues: SkillValidationIssue[]): {
className: string; className: string;
title: string; title: string;
Icon: typeof AlertTriangle; Icon: typeof AlertTriangle;

View file

@ -41,8 +41,8 @@ import { validateSkillFolderName } from './skillValidationUtils';
import type { import type {
SkillDetail, SkillDetail,
SkillInvocationMode, SkillInvocationMode,
SkillRootKind,
SkillReviewPreview, SkillReviewPreview,
SkillRootKind,
} from '@shared/types/extensions'; } from '@shared/types/extensions';
type EditorMode = 'create' | 'edit'; type EditorMode = 'create' | 'edit';

View file

@ -24,8 +24,8 @@ import { SKILL_ROOT_DEFINITIONS } from '@shared/utils/skillRoots';
import { FileSearch, FolderOpen, X } from 'lucide-react'; import { FileSearch, FolderOpen, X } from 'lucide-react';
import { getSuggestedSkillFolderNameFromPath } from './skillFolderNameUtils'; import { getSuggestedSkillFolderNameFromPath } from './skillFolderNameUtils';
import { SkillReviewDialog } from './SkillReviewDialog';
import { resolveSkillProjectPath } from './skillProjectUtils'; import { resolveSkillProjectPath } from './skillProjectUtils';
import { SkillReviewDialog } from './SkillReviewDialog';
import { validateSkillFolderName, validateSkillImportSourceDir } from './skillValidationUtils'; import { validateSkillFolderName, validateSkillImportSourceDir } from './skillValidationUtils';
import type { SkillReviewPreview, SkillRootKind } from '@shared/types/extensions'; import type { SkillReviewPreview, SkillRootKind } from '@shared/types/extensions';

View file

@ -127,7 +127,7 @@ function formatRuntimeAudienceLabel(providerNames: readonly string[]): string {
return 'the configured runtime'; return 'the configured runtime';
} }
if (providerNames.length === 1) { if (providerNames.length === 1) {
return providerNames[0]!; return providerNames[0];
} }
if (providerNames.length === 2) { if (providerNames.length === 2) {
return `${providerNames[0]} and ${providerNames[1]}`; return `${providerNames[0]} and ${providerNames[1]}`;
@ -165,7 +165,7 @@ export const SkillsPanel = ({
const [successMessage, setSuccessMessage] = useState<string | null>(null); const [successMessage, setSuccessMessage] = useState<string | null>(null);
const [highlightedSkillId, setHighlightedSkillId] = useState<string | null>(null); const [highlightedSkillId, setHighlightedSkillId] = useState<string | null>(null);
const selectedSkillIdRef = useRef<string | null>(selectedSkillId); const selectedSkillIdRef = useRef<string | null>(selectedSkillId);
const selectedSkillItemRef = useRef<SkillCatalogItem | SkillDetail['item'] | null>(null); const selectedSkillItemRef = useRef<SkillCatalogItem | null>(null);
selectedSkillIdRef.current = selectedSkillId; selectedSkillIdRef.current = selectedSkillId;
const mergedSkills = useMemo( const mergedSkills = useMemo(

View file

@ -1,8 +1,8 @@
import { cn } from '@renderer/lib/utils';
import { import {
getTeamModelBadgeLabel, getTeamModelBadgeLabel,
getVisibleTeamProviderModels, getVisibleTeamProviderModels,
} from '@renderer/utils/teamModelCatalog'; } from '@renderer/utils/teamModelCatalog';
import { cn } from '@renderer/lib/utils';
import type { import type {
CliProviderId, CliProviderId,
@ -43,7 +43,7 @@ function getAvailabilityChip(status: CliProviderModelAvailabilityStatus | null):
} }
} }
export function ProviderModelBadges({ export const ProviderModelBadges = ({
providerId, providerId,
models, models,
modelAvailability, modelAvailability,
@ -53,7 +53,7 @@ export function ProviderModelBadges({
readonly models: string[]; readonly models: string[];
readonly modelAvailability?: CliProviderModelAvailability[]; readonly modelAvailability?: CliProviderModelAvailability[];
readonly providerStatus?: Pick<CliProviderStatus, 'providerId' | 'authMethod' | 'backend'> | null; readonly providerStatus?: Pick<CliProviderStatus, 'providerId' | 'authMethod' | 'backend'> | null;
}): React.JSX.Element { }): React.JSX.Element => {
const visibleModels = getVisibleTeamProviderModels(providerId, models, providerStatus); const visibleModels = getVisibleTeamProviderModels(providerId, models, providerStatus);
return ( return (
@ -94,4 +94,4 @@ export function ProviderModelBadges({
})} })}
</div> </div>
); );
} };

View file

@ -10,7 +10,6 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
import { isElectronMode } from '@renderer/api'; import { isElectronMode } from '@renderer/api';
import { confirm } from '@renderer/components/common/ConfirmDialog'; import { confirm } from '@renderer/components/common/ConfirmDialog';
import { ProviderBrandLogo } from '@renderer/components/common/ProviderBrandLogo'; import { ProviderBrandLogo } from '@renderer/components/common/ProviderBrandLogo';
import { ProviderModelBadges } from '@renderer/components/runtime/ProviderModelBadges';
import { import {
formatProviderStatusText, formatProviderStatusText,
getProviderConnectionModeSummary, getProviderConnectionModeSummary,
@ -21,6 +20,7 @@ import {
isConnectionManagedRuntimeProvider, isConnectionManagedRuntimeProvider,
shouldShowProviderConnectAction, shouldShowProviderConnectAction,
} from '@renderer/components/runtime/providerConnectionUi'; } from '@renderer/components/runtime/providerConnectionUi';
import { ProviderModelBadges } from '@renderer/components/runtime/ProviderModelBadges';
import { getProviderRuntimeBackendSummary } from '@renderer/components/runtime/ProviderRuntimeBackendSelector'; import { getProviderRuntimeBackendSummary } from '@renderer/components/runtime/ProviderRuntimeBackendSelector';
import { ProviderRuntimeSettingsDialog } from '@renderer/components/runtime/ProviderRuntimeSettingsDialog'; import { ProviderRuntimeSettingsDialog } from '@renderer/components/runtime/ProviderRuntimeSettingsDialog';
import { SettingsToggle } from '@renderer/components/settings/components'; import { SettingsToggle } from '@renderer/components/settings/components';

View file

@ -45,7 +45,10 @@ import {
getTeamModelSelectionError, getTeamModelSelectionError,
normalizeTeamModelForUi, normalizeTeamModelForUi,
} from '@renderer/utils/teamModelAvailability'; } 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 { DEFAULT_PROVIDER_MODEL_SELECTION } from '@shared/utils/providerModelSelection';
import { isTeamProviderId, normalizeOptionalTeamProviderId } from '@shared/utils/teamProvider'; import { isTeamProviderId, normalizeOptionalTeamProviderId } from '@shared/utils/teamProvider';
import { AlertTriangle, CheckCircle2, Info, Loader2, X } from 'lucide-react'; 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 { AdvancedCliSection } from './AdvancedCliSection';
import { OptionalSettingsSection } from './OptionalSettingsSection'; import { OptionalSettingsSection } from './OptionalSettingsSection';
import { ProjectPathSelector } from './ProjectPathSelector'; import { ProjectPathSelector } from './ProjectPathSelector';
import {
getProviderPrepareCachedSnapshot,
type ProviderPrepareDiagnosticsModelResult,
runProviderPrepareDiagnostics,
} from './providerPrepareDiagnostics';
import { getProvisioningModelIssue } from './provisioningModelIssues';
import { import {
failIncompleteProviderChecks, failIncompleteProviderChecks,
getProvisioningFailureHint,
getPrimaryProvisioningFailureDetail, getPrimaryProvisioningFailureDetail,
getProvisioningFailureHint,
getProvisioningProviderBackendSummary, getProvisioningProviderBackendSummary,
type ProvisioningProviderCheck, type ProvisioningProviderCheck,
ProvisioningProviderStatusList, ProvisioningProviderStatusList,
shouldHideProvisioningProviderStatusList, shouldHideProvisioningProviderStatusList,
updateProviderCheck, updateProviderCheck,
} from './ProvisioningProviderStatusList'; } from './ProvisioningProviderStatusList';
import { getProvisioningModelIssue } from './provisioningModelIssues';
import {
getProviderPrepareCachedSnapshot,
runProviderPrepareDiagnostics,
type ProviderPrepareDiagnosticsModelResult,
} from './providerPrepareDiagnostics';
import { SkipPermissionsCheckbox } from './SkipPermissionsCheckbox'; import { SkipPermissionsCheckbox } from './SkipPermissionsCheckbox';
import { computeEffectiveTeamModel } from './TeamModelSelector'; import { computeEffectiveTeamModel } from './TeamModelSelector';
import { getNextSuggestedTeamName } from './teamNameSets'; import { getNextSuggestedTeamName } from './teamNameSets';
@ -108,7 +111,7 @@ function getStoredTeamModel(providerId: TeamProviderId): string {
if (stored === null) { if (stored === null) {
return providerId === 'anthropic' ? 'opus' : ''; return providerId === 'anthropic' ? 'opus' : '';
} }
return normalizeTeamModelForUi(providerId, stored === '__default__' ? '' : stored); return normalizeCatalogTeamModelForUi(providerId, stored === '__default__' ? '' : stored);
} }
function isEphemeralRenderedProjectPath(projectPath: string | null | undefined): boolean { function isEphemeralRenderedProjectPath(projectPath: string | null | undefined): boolean {

View file

@ -47,7 +47,10 @@ import {
getTeamModelSelectionError, getTeamModelSelectionError,
normalizeTeamModelForUi, normalizeTeamModelForUi,
} from '@renderer/utils/teamModelAvailability'; } 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 { DEFAULT_PROVIDER_MODEL_SELECTION } from '@shared/utils/providerModelSelection';
import { isTeamProviderId, normalizeOptionalTeamProviderId } from '@shared/utils/teamProvider'; import { isTeamProviderId, normalizeOptionalTeamProviderId } from '@shared/utils/teamProvider';
import { import {
@ -69,22 +72,22 @@ import { EffortLevelSelector } from './EffortLevelSelector';
import { resolveLaunchDialogPrefill } from './launchDialogPrefill'; import { resolveLaunchDialogPrefill } from './launchDialogPrefill';
import { OptionalSettingsSection } from './OptionalSettingsSection'; import { OptionalSettingsSection } from './OptionalSettingsSection';
import { ProjectPathSelector } from './ProjectPathSelector'; import { ProjectPathSelector } from './ProjectPathSelector';
import {
getProviderPrepareCachedSnapshot,
type ProviderPrepareDiagnosticsModelResult,
runProviderPrepareDiagnostics,
} from './providerPrepareDiagnostics';
import { getProvisioningModelIssue } from './provisioningModelIssues';
import { import {
failIncompleteProviderChecks, failIncompleteProviderChecks,
getProvisioningFailureHint,
getPrimaryProvisioningFailureDetail, getPrimaryProvisioningFailureDetail,
getProvisioningFailureHint,
getProvisioningProviderBackendSummary, getProvisioningProviderBackendSummary,
type ProvisioningProviderCheck, type ProvisioningProviderCheck,
ProvisioningProviderStatusList, ProvisioningProviderStatusList,
shouldHideProvisioningProviderStatusList, shouldHideProvisioningProviderStatusList,
updateProviderCheck, updateProviderCheck,
} from './ProvisioningProviderStatusList'; } from './ProvisioningProviderStatusList';
import { getProvisioningModelIssue } from './provisioningModelIssues';
import {
getProviderPrepareCachedSnapshot,
runProviderPrepareDiagnostics,
type ProviderPrepareDiagnosticsModelResult,
} from './providerPrepareDiagnostics';
import { import {
computeEffectiveTeamModel, computeEffectiveTeamModel,
formatTeamModelSummary, formatTeamModelSummary,
@ -192,7 +195,7 @@ function getStoredTeamModel(providerId: TeamProviderId): string {
if (stored === null) { if (stored === null) {
return providerId === 'anthropic' ? 'opus' : ''; return providerId === 'anthropic' ? 'opus' : '';
} }
return normalizeTeamModelForUi(providerId, stored === '__default__' ? '' : stored); return normalizeCatalogTeamModelForUi(providerId, stored === '__default__' ? '' : stored);
} }
function getProviderLabel(providerId: TeamProviderId): string { function getProviderLabel(providerId: TeamProviderId): string {

View file

@ -11,7 +11,6 @@ import {
} from '@renderer/components/ui/tooltip'; } from '@renderer/components/ui/tooltip';
import { cn } from '@renderer/lib/utils'; import { cn } from '@renderer/lib/utils';
import { useStore } from '@renderer/store'; import { useStore } from '@renderer/store';
import { getAnthropicDefaultTeamModel } from '@shared/utils/anthropicModelDefaults';
import { import {
GEMINI_UI_DISABLED_BADGE_LABEL, GEMINI_UI_DISABLED_BADGE_LABEL,
GEMINI_UI_DISABLED_REASON, GEMINI_UI_DISABLED_REASON,
@ -30,6 +29,7 @@ import {
getTeamProviderLabel as getCatalogTeamProviderLabel, getTeamProviderLabel as getCatalogTeamProviderLabel,
} from '@renderer/utils/teamModelCatalog'; } from '@renderer/utils/teamModelCatalog';
import { extractProviderScopedBaseModel } from '@renderer/utils/teamModelContext'; import { extractProviderScopedBaseModel } from '@renderer/utils/teamModelContext';
import { getAnthropicDefaultTeamModel } from '@shared/utils/anthropicModelDefaults';
import { AlertTriangle, Info } from 'lucide-react'; import { AlertTriangle, Info } from 'lucide-react';
export { getProviderScopedTeamModelLabel } from '@renderer/utils/teamModelCatalog'; export { getProviderScopedTeamModelLabel } from '@renderer/utils/teamModelCatalog';

View file

@ -1,5 +1,5 @@
import { normalizeCreateLaunchProviderForUi } from '@renderer/utils/geminiUiFreeze'; 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 { extractProviderScopedBaseModel } from '@renderer/utils/teamModelContext';
import { isLeadMember } from '@shared/utils/leadDetection'; import { isLeadMember } from '@shared/utils/leadDetection';
import { normalizeOptionalTeamProviderId } from '@shared/utils/teamProvider'; import { normalizeOptionalTeamProviderId } from '@shared/utils/teamProvider';
@ -102,7 +102,7 @@ export function resolveLaunchDialogPrefill({
return { return {
providerId, providerId,
model: matchingModel model: matchingModel
? normalizeTeamModelForUi(providerId, matchingModel) ? normalizeCatalogTeamModelForUi(providerId, matchingModel)
: getStoredModel(providerId), : getStoredModel(providerId),
effort, effort,
limitContext, limitContext,

View file

@ -5,15 +5,13 @@ import type { TeamProviderId, TeamProvisioningPrepareResult } from '@shared/type
export type ProviderPrepareCheckStatus = 'ready' | 'notes' | 'failed'; export type ProviderPrepareCheckStatus = 'ready' | 'notes' | 'failed';
interface PrepareProvisioningFn { type PrepareProvisioningFn = (
( cwd?: string,
cwd?: string, providerId?: TeamProviderId,
providerId?: TeamProviderId, providerIds?: TeamProviderId[],
providerIds?: TeamProviderId[], selectedModels?: string[],
selectedModels?: string[], limitContext?: boolean
limitContext?: boolean ) => Promise<TeamProvisioningPrepareResult>;
): Promise<TeamProvisioningPrepareResult>;
}
interface ProviderPrepareDiagnosticsProgress { interface ProviderPrepareDiagnosticsProgress {
details: string[]; details: string[];
@ -156,15 +154,15 @@ function normalizeModelReason(rawReason: string | null | undefined): string | nu
return 'Model verification timed out'; return 'Model verification timed out';
} }
const detailMatch = trimmed.match(/"detail":"((?:\\"|[^"])*)"/i); const detailMatch = /"detail":"((?:\\"|[^"])*)"/i.exec(trimmed);
if (detailMatch?.[1]) { if (detailMatch?.[1]) {
return normalizeModelReason(detailMatch[1].replace(/\\"/g, '"').trim()); return normalizeModelReason(detailMatch[1].replace(/\\"/g, '"').trim());
} }
const messageMatch = trimmed.match(/"message":"((?:\\"|[^"])*)"/i); const messageMatch = /"message":"((?:\\"|[^"])*)"/i.exec(trimmed);
if (messageMatch?.[1]) { if (messageMatch?.[1]) {
const decodedMessage = messageMatch[1].replace(/\\"/g, '"'); const decodedMessage = messageMatch[1].replace(/\\"/g, '"');
const nestedDetailMatch = decodedMessage.match(/"detail":"([^"]+)"/i); const nestedDetailMatch = /"detail":"([^"]+)"/i.exec(decodedMessage);
if (nestedDetailMatch?.[1]) { if (nestedDetailMatch?.[1]) {
return normalizeModelReason(nestedDetailMatch[1].trim()); return normalizeModelReason(nestedDetailMatch[1].trim());
} }

View file

@ -111,8 +111,7 @@ export const MemberCard = ({
!isRemoved && !isRemoved &&
presenceLabel === 'starting' && presenceLabel === 'starting' &&
spawnLaunchState !== 'failed_to_start' && spawnLaunchState !== 'failed_to_start' &&
!activityTask && !activityTask;
!runtimeSummary;
const showStartingBadge = !isRemoved && presenceLabel === 'starting' && !activityTask; const showStartingBadge = !isRemoved && presenceLabel === 'starting' && !activityTask;
const showRuntimeAdvisoryBadge = const showRuntimeAdvisoryBadge =
!isRemoved && !isRemoved &&

View file

@ -16,8 +16,8 @@ import { getTeamColorSet } from '@renderer/constants/teamColors';
import { useDraftPersistence } from '@renderer/hooks/useDraftPersistence'; import { useDraftPersistence } from '@renderer/hooks/useDraftPersistence';
import { useFileListCacheWarmer } from '@renderer/hooks/useFileListCacheWarmer'; import { useFileListCacheWarmer } from '@renderer/hooks/useFileListCacheWarmer';
import { useTheme } from '@renderer/hooks/useTheme'; import { useTheme } from '@renderer/hooks/useTheme';
import { reconcileChips, removeChipTokenFromText } from '@renderer/utils/chipUtils';
import { cn } from '@renderer/lib/utils'; import { cn } from '@renderer/lib/utils';
import { reconcileChips, removeChipTokenFromText } from '@renderer/utils/chipUtils';
import { getMemberColorByName } from '@shared/constants/memberColors'; import { getMemberColorByName } from '@shared/constants/memberColors';
import { AlertTriangle, ChevronDown, ChevronRight, Info, RotateCcw, Trash2 } from 'lucide-react'; import { AlertTriangle, ChevronDown, ChevronRight, Info, RotateCcw, Trash2 } from 'lucide-react';

View file

@ -1,7 +1,7 @@
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { resolveMemberRuntimeSummary } from '@renderer/utils/memberRuntimeSummary';
import { buildMemberColorMap } from '@renderer/utils/memberHelpers'; import { buildMemberColorMap } from '@renderer/utils/memberHelpers';
import { resolveMemberRuntimeSummary } from '@renderer/utils/memberRuntimeSummary';
import { isLeadMember } from '@shared/utils/leadDetection'; import { isLeadMember } from '@shared/utils/leadDetection';
import { MemberCard } from './MemberCard'; import { MemberCard } from './MemberCard';

View file

@ -3,6 +3,8 @@ import { serializeChipsWithText } from '@renderer/types/inlineChip';
import { normalizeCreateLaunchProviderForUi } from '@renderer/utils/geminiUiFreeze'; import { normalizeCreateLaunchProviderForUi } from '@renderer/utils/geminiUiFreeze';
import { buildMemberColorMap } from '@renderer/utils/memberHelpers'; import { buildMemberColorMap } from '@renderer/utils/memberHelpers';
import { normalizeTeamModelForUi } from '@renderer/utils/teamModelAvailability'; 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 { isLeadMember } from '@shared/utils/leadDetection';
import { normalizeOptionalTeamProviderId } from '@shared/utils/teamProvider'; import { normalizeOptionalTeamProviderId } from '@shared/utils/teamProvider';
@ -32,6 +34,7 @@ function newDraftId(): string {
export function createMemberDraft(initial?: Partial<MemberDraft>): MemberDraft { export function createMemberDraft(initial?: Partial<MemberDraft>): MemberDraft {
const providerId = initial?.providerId; const providerId = initial?.providerId;
const normalizedModel = extractProviderScopedBaseModel(initial?.model ?? '', providerId) ?? '';
return { return {
id: initial?.id ?? newDraftId(), id: initial?.id ?? newDraftId(),
name: initial?.name ?? '', name: initial?.name ?? '',
@ -39,7 +42,7 @@ export function createMemberDraft(initial?: Partial<MemberDraft>): MemberDraft {
customRole: initial?.customRole ?? '', customRole: initial?.customRole ?? '',
workflow: initial?.workflow, workflow: initial?.workflow,
providerId, providerId,
model: normalizeTeamModelForUi(providerId, initial?.model ?? ''), model: normalizeCatalogTeamModelForUi(providerId, normalizedModel),
effort: initial?.effort, effort: initial?.effort,
removedAt: initial?.removedAt, removedAt: initial?.removedAt,
}; };

View file

@ -4,14 +4,14 @@
*/ */
import { api } from '@renderer/api'; import { api } from '@renderer/api';
import { isProjectScopedMcpScope } from '@shared/utils/mcpScopes';
import { import {
getExtensionActionDisableReason, getExtensionActionDisableReason,
getMcpDiagnosticKey, getMcpDiagnosticKey,
getMcpProjectStateKey,
getMcpOperationKey, getMcpOperationKey,
getMcpProjectStateKey,
getPluginOperationKey, getPluginOperationKey,
} from '@shared/utils/extensionNormalizers'; } from '@shared/utils/extensionNormalizers';
import { isProjectScopedMcpScope } from '@shared/utils/mcpScopes';
import { findPaneByTabId, updatePane } from '../utils/paneHelpers'; import { findPaneByTabId, updatePane } from '../utils/paneHelpers';

View file

@ -94,10 +94,10 @@ type TeamGraphConfigMemberSeedInput = Pick<
NonNullable<TeamData['config']['members']>[number], NonNullable<TeamData['config']['members']>[number],
'name' | 'agentId' | 'removedAt' 'name' | 'agentId' | 'removedAt'
>; >;
type TeamGraphLayoutSessionState = { interface TeamGraphLayoutSessionState {
mode: 'default' | 'manual'; mode: 'default' | 'manual';
signature: string | null; signature: string | null;
}; }
export function isTeamDataRefreshPending(teamName: string): boolean { export function isTeamDataRefreshPending(teamName: string): boolean {
return ( return (
@ -1025,8 +1025,7 @@ function areTeamGraphSlotAssignmentsEqual(
for (const [stableOwnerId, leftAssignment] of leftEntries) { for (const [stableOwnerId, leftAssignment] of leftEntries) {
const rightAssignment = right?.[stableOwnerId]; const rightAssignment = right?.[stableOwnerId];
if ( if (
!rightAssignment || rightAssignment?.ringIndex !== leftAssignment.ringIndex ||
rightAssignment.ringIndex !== leftAssignment.ringIndex ||
rightAssignment.sectorIndex !== leftAssignment.sectorIndex rightAssignment.sectorIndex !== leftAssignment.sectorIndex
) { ) {
return false; return false;
@ -2063,8 +2062,7 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
return ( return (
(nextAssignment.ringIndex === assignment.ringIndex && (nextAssignment.ringIndex === assignment.ringIndex &&
nextAssignment.sectorIndex === assignment.sectorIndex) || nextAssignment.sectorIndex === assignment.sectorIndex) ||
(displacedAssignment != null && (nextAssignment.ringIndex === displacedAssignment?.ringIndex &&
nextAssignment.ringIndex === displacedAssignment.ringIndex &&
nextAssignment.sectorIndex === displacedAssignment.sectorIndex) nextAssignment.sectorIndex === displacedAssignment.sectorIndex)
); );
} }

View file

@ -1,8 +1,8 @@
import { formatTeamModelSummary } from '@renderer/components/team/dialogs/TeamModelSelector'; import { formatTeamModelSummary } from '@renderer/components/team/dialogs/TeamModelSelector';
import { inferTeamProviderIdFromModel } from '@shared/utils/teamProvider';
import type { TeamLaunchParams } from '@renderer/store/slices/teamSlice'; import type { TeamLaunchParams } from '@renderer/store/slices/teamSlice';
import type { MemberSpawnStatusEntry, ResolvedTeamMember, TeamProviderId } from '@shared/types'; import type { MemberSpawnStatusEntry, ResolvedTeamMember, TeamProviderId } from '@shared/types';
import { inferTeamProviderIdFromModel } from '@shared/utils/teamProvider';
function isMemberLaunchPending(spawnEntry: MemberSpawnStatusEntry | undefined): boolean { function isMemberLaunchPending(spawnEntry: MemberSpawnStatusEntry | undefined): boolean {
if (!spawnEntry) { if (!spawnEntry) {

View file

@ -2,8 +2,8 @@ import { getSkillAudienceLabel, isSkillAvailableForProvider } from '@shared/util
import { isSupportedSlashCommandName } from '@shared/utils/slashCommands'; import { isSupportedSlashCommandName } from '@shared/utils/slashCommands';
import type { MentionSuggestion } from '@renderer/types/mention'; import type { MentionSuggestion } from '@renderer/types/mention';
import type { SkillCatalogItem } from '@shared/types/extensions';
import type { TeamProviderId } from '@shared/types'; import type { TeamProviderId } from '@shared/types';
import type { SkillCatalogItem } from '@shared/types/extensions';
import type { KnownSlashCommandDefinition } from '@shared/utils/slashCommands'; import type { KnownSlashCommandDefinition } from '@shared/utils/slashCommands';
function orderSkillsForProvider( function orderSkillsForProvider(

View file

@ -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 { import type {
CliProviderId, CliProviderId,
CliProviderModelAvailability, CliProviderModelAvailability,
@ -6,29 +25,10 @@ import type {
TeamProviderId, TeamProviderId,
} from '@shared/types'; } 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 { 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_MODEL,
GPT_5_1_CODEX_MINI_UI_DISABLED_REASON, 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_MODEL,
GPT_5_2_CODEX_UI_DISABLED_REASON, GPT_5_2_CODEX_UI_DISABLED_REASON,
GPT_5_3_CODEX_SPARK_UI_DISABLED_MODEL, GPT_5_3_CODEX_SPARK_UI_DISABLED_MODEL,

View file

@ -1,4 +1,3 @@
import type { CliProviderId, CliProviderStatus, TeamProviderId } from '@shared/types';
import { import {
filterVisibleProviderRuntimeModels, filterVisibleProviderRuntimeModels,
GPT_5_1_CODEX_MINI_UI_DISABLED_MODEL, GPT_5_1_CODEX_MINI_UI_DISABLED_MODEL,
@ -6,6 +5,8 @@ import {
GPT_5_3_CODEX_SPARK_UI_DISABLED_MODEL, GPT_5_3_CODEX_SPARK_UI_DISABLED_MODEL,
} from '@shared/utils/providerModelVisibility'; } from '@shared/utils/providerModelVisibility';
import type { CliProviderId, CliProviderStatus, TeamProviderId } from '@shared/types';
export { export {
GPT_5_1_CODEX_MINI_UI_DISABLED_MODEL, GPT_5_1_CODEX_MINI_UI_DISABLED_MODEL,
GPT_5_2_CODEX_UI_DISABLED_MODEL, GPT_5_2_CODEX_UI_DISABLED_MODEL,

View file

@ -9,9 +9,9 @@ import {
import type { import type {
CliInstallationStatus, CliInstallationStatus,
InstallScope,
InstalledMcpEntry, InstalledMcpEntry,
InstalledPluginEntry, InstalledPluginEntry,
InstallScope,
PluginCapability, PluginCapability,
PluginCatalogItem, PluginCatalogItem,
} from '@shared/types'; } from '@shared/types';
@ -206,7 +206,7 @@ export function getPreferredMcpInstallationEntry(
return [...installations].sort( return [...installations].sort(
(left, right) => MCP_SCOPE_PRIORITY[left.scope] - MCP_SCOPE_PRIORITY[right.scope] (left, right) => MCP_SCOPE_PRIORITY[left.scope] - MCP_SCOPE_PRIORITY[right.scope]
)[0]!; )[0];
} }
/** /**

View file

@ -231,7 +231,7 @@ describe('CliInstallerService', () => {
verificationState: 'verified', verificationState: 'verified',
modelVerificationState: 'idle', modelVerificationState: 'idle',
statusMessage: null, statusMessage: null,
models: ['gpt-5.4', 'gpt-5.2-codex'], models: ['gpt-5.4', 'gpt-5.4-mini'],
modelAvailability: [], modelAvailability: [],
canLoginFromUi: true, canLoginFromUi: true,
capabilities: { teamLaunch: true, oneShot: true }, capabilities: { teamLaunch: true, oneShot: true },
@ -267,14 +267,12 @@ describe('CliInstallerService', () => {
if (normalizedArgs === '--version') { if (normalizedArgs === '--version') {
return { stdout: '2.3.4', stderr: '' }; 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')) { if (normalizedArgs.includes('--model gpt-5.4')) {
return { stdout: 'PONG', stderr: '' }; 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}`); throw new Error(`Unexpected execCli call: ${normalizedArgs}`);
}); });
@ -291,7 +289,7 @@ describe('CliInstallerService', () => {
expect(verifiedProvider?.modelAvailability).toEqual( expect(verifiedProvider?.modelAvailability).toEqual(
expect.arrayContaining([ expect.arrayContaining([
expect.objectContaining({ modelId: 'gpt-5.4', status: 'checking' }), 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(latestCodexProvider?.modelAvailability).toEqual([
expect.objectContaining({ modelId: 'gpt-5.4', status: 'available' }), expect.objectContaining({ modelId: 'gpt-5.4', status: 'available' }),
expect.objectContaining({ expect.objectContaining({
modelId: 'gpt-5.2-codex', modelId: 'gpt-5.4-mini',
status: 'unavailable', status: 'unavailable',
}), }),
]); ]);

View file

@ -40,7 +40,7 @@ interface StoreState {
installed?: boolean; installed?: boolean;
binaryPath?: string | null; binaryPath?: string | null;
launchError?: string | null; launchError?: string | null;
}; } | null;
} }
const storeState = {} as StoreState; const storeState = {} as StoreState;

View file

@ -4,6 +4,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import type { CliInstallationStatus } from '@shared/types'; import type { CliInstallationStatus } from '@shared/types';
import type { SkillCatalogItem } from '@shared/types/extensions'; import type { SkillCatalogItem } from '@shared/types/extensions';
import { createDefaultCliExtensionCapabilities } from '@shared/utils/providerExtensionCapabilities';
interface StoreState { interface StoreState {
fetchSkillsCatalog: ReturnType<typeof vi.fn>; fetchSkillsCatalog: ReturnType<typeof vi.fn>;
@ -213,12 +214,9 @@ function makeMultimodelStatus(
capabilities: { capabilities: {
teamLaunch: true, teamLaunch: true,
oneShot: true, oneShot: true,
extensions: { extensions: createDefaultCliExtensionCapabilities({
plugins: { status: 'supported', ownership: 'provider', reason: null }, plugins: { status: 'supported', ownership: 'provider-scoped', reason: null },
mcp: { status: 'supported', ownership: 'shared', reason: null }, }),
skills: { status: 'supported', ownership: 'shared', reason: null },
apiKeys: { status: 'supported', ownership: 'shared', reason: null },
},
}, },
connection: null, connection: null,
backend: null, backend: null,
@ -405,12 +403,9 @@ describe('SkillsPanel', () => {
capabilities: { capabilities: {
teamLaunch: true, teamLaunch: true,
oneShot: true, oneShot: true,
extensions: { extensions: createDefaultCliExtensionCapabilities({
plugins: { status: 'unsupported', ownership: 'provider', reason: null }, plugins: { status: 'unsupported', ownership: 'provider-scoped', reason: null },
mcp: { status: 'supported', ownership: 'shared', reason: null }, }),
skills: { status: 'supported', ownership: 'shared', reason: null },
apiKeys: { status: 'supported', ownership: 'shared', reason: null },
},
}, },
connection: null, connection: null,
backend: null, backend: null,

View file

@ -55,12 +55,14 @@ vi.mock('../../../src/renderer/api', () => ({
})); }));
import { api } from '../../../src/renderer/api'; import { api } from '../../../src/renderer/api';
import type { CliInstallationStatus } from '../../../src/shared/types';
import { import {
getMcpDiagnosticKey, getMcpDiagnosticKey,
getMcpProjectStateKey, getMcpProjectStateKey,
getMcpOperationKey, getMcpOperationKey,
getPluginOperationKey, getPluginOperationKey,
} from '../../../src/shared/utils/extensionNormalizers'; } from '../../../src/shared/utils/extensionNormalizers';
import { createDefaultCliExtensionCapabilities } from '../../../src/shared/utils/providerExtensionCapabilities';
import type { import type {
EnrichedPlugin, EnrichedPlugin,
@ -137,7 +139,7 @@ const makeSkillDetail = (overrides: Partial<SkillDetail> = {}): SkillDetail => (
...overrides, ...overrides,
}); });
const makeReadyCliStatus = () => ({ const makeReadyCliStatus = (): CliInstallationStatus => ({
flavor: 'claude' as const, flavor: 'claude' as const,
displayName: 'Claude', displayName: 'Claude',
supportsSelfUpdate: true, supportsSelfUpdate: true,
@ -154,7 +156,10 @@ const makeReadyCliStatus = () => ({
providers: [], providers: [],
}); });
const makeLimitedMultimodelCliStatus = (section: 'plugins' | 'mcp', reason: string) => ({ const makeLimitedMultimodelCliStatus = (
section: 'plugins' | 'mcp',
reason: string
): CliInstallationStatus => ({
flavor: 'agent_teams_orchestrator' as const, flavor: 'agent_teams_orchestrator' as const,
displayName: 'Claude Multimodel', displayName: 'Claude Multimodel',
supportsSelfUpdate: false, supportsSelfUpdate: false,
@ -181,21 +186,22 @@ const makeLimitedMultimodelCliStatus = (section: 'plugins' | 'mcp', reason: stri
capabilities: { capabilities: {
teamLaunch: true, teamLaunch: true,
oneShot: true, oneShot: true,
extensions: { extensions: createDefaultCliExtensionCapabilities({
plugins: { plugins: {
status: section === 'plugins' ? 'unsupported' : 'supported', status: section === 'plugins' ? 'unsupported' : 'supported',
ownership: 'shared' as const, ownership: 'shared',
reason: section === 'plugins' ? reason : null, reason: section === 'plugins' ? reason : null,
}, },
mcp: { mcp: {
status: section === 'mcp' ? 'read-only' : 'supported', status: section === 'mcp' ? 'read-only' : 'supported',
ownership: 'shared' as const, ownership: 'shared',
reason: section === 'mcp' ? reason : null, 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,
}, },
], ],
}); });

View file

@ -7,6 +7,7 @@ import {
} from '@renderer/utils/multimodelProviderVisibility'; } from '@renderer/utils/multimodelProviderVisibility';
import type { CliInstallationStatus, CliProviderStatus } from '@shared/types'; import type { CliInstallationStatus, CliProviderStatus } from '@shared/types';
import { createDefaultCliExtensionCapabilities } from '@shared/utils/providerExtensionCapabilities';
function createProvider(providerId: CliProviderStatus['providerId']): CliProviderStatus { function createProvider(providerId: CliProviderStatus['providerId']): CliProviderStatus {
return { return {
@ -21,9 +22,9 @@ function createProvider(providerId: CliProviderStatus['providerId']): CliProvide
capabilities: { capabilities: {
teamLaunch: true, teamLaunch: true,
oneShot: true, oneShot: true,
extensions: createDefaultCliExtensionCapabilities(),
}, },
statusMessage: null, statusMessage: null,
detailMessage: null,
selectedBackendId: null, selectedBackendId: null,
resolvedBackendId: null, resolvedBackendId: null,
availableBackends: [], availableBackends: [],