diff --git a/src/renderer/components/extensions/skills/SkillsPanel.tsx b/src/renderer/components/extensions/skills/SkillsPanel.tsx
index ecdeed73..e6bf795a 100644
--- a/src/renderer/components/extensions/skills/SkillsPanel.tsx
+++ b/src/renderer/components/extensions/skills/SkillsPanel.tsx
@@ -6,6 +6,11 @@ import { Button } from '@renderer/components/ui/button';
import { Popover, PopoverContent, PopoverTrigger } from '@renderer/components/ui/popover';
import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip';
import { useStore } from '@renderer/store';
+import { getVisibleMultimodelProviders } from '@renderer/utils/multimodelProviderVisibility';
+import {
+ getCliProviderExtensionCapability,
+ isCliExtensionCapabilityAvailable,
+} from '@shared/utils/providerExtensionCapabilities';
import {
formatSkillRootKind,
getSkillAudience,
@@ -90,6 +95,19 @@ function getSkillStatus(skill: SkillCatalogItem): string {
return 'Ready to use';
}
+function formatRuntimeAudienceLabel(providerNames: readonly string[]): string {
+ if (providerNames.length === 0) {
+ return 'the configured runtime';
+ }
+ if (providerNames.length === 1) {
+ return providerNames[0]!;
+ }
+ if (providerNames.length === 2) {
+ return `${providerNames[0]} and ${providerNames[1]}`;
+ }
+ return `${providerNames.slice(0, -1).join(', ')}, and ${providerNames.at(-1)}`;
+}
+
export const SkillsPanel = ({
projectPath,
projectLabel,
@@ -131,6 +149,19 @@ export const SkillsPanel = ({
() => isCodexSkillOverlayAvailable(cliStatus),
[cliStatus]
);
+ const skillsAudienceLabel = useMemo(() => {
+ if (cliStatus?.flavor !== 'agent_teams_orchestrator') {
+ return null;
+ }
+
+ const providerNames = getVisibleMultimodelProviders(cliStatus.providers ?? [])
+ .filter((provider) =>
+ isCliExtensionCapabilityAvailable(getCliProviderExtensionCapability(provider, 'skills'))
+ )
+ .map((provider) => provider.displayName);
+
+ return formatRuntimeAudienceLabel(providerNames);
+ }, [cliStatus]);
const codexOnlySkillsCount = useMemo(
() => mergedSkills.filter((skill) => getSkillAudience(skill.rootKind) === 'codex').length,
[mergedSkills]
@@ -252,8 +283,9 @@ export const SkillsPanel = ({
{cliStatus?.flavor === 'agent_teams_orchestrator' && (
- Shared skills in `.claude`, `.cursor`, and `.agents` are available to both Anthropic and
- Codex. Skills stored in `.codex` are only offered to Codex sessions.
+ Shared skills in `.claude`, `.cursor`, and `.agents` are available to{' '}
+ {skillsAudienceLabel ?? 'the configured runtime'}. Skills stored in `.codex` stay
+ Codex-only when Codex support is available.
)}
diff --git a/test/renderer/components/extensions/skills/SkillsPanel.test.ts b/test/renderer/components/extensions/skills/SkillsPanel.test.ts
index d8864a79..557794cb 100644
--- a/test/renderer/components/extensions/skills/SkillsPanel.test.ts
+++ b/test/renderer/components/extensions/skills/SkillsPanel.test.ts
@@ -168,6 +168,52 @@ function makeUserSkill(): SkillCatalogItem {
};
}
+function makeMultimodelStatus(
+ overrides?: Partial
+): CliInstallationStatus {
+ return {
+ flavor: 'agent_teams_orchestrator',
+ displayName: 'Multimodel runtime',
+ supportsSelfUpdate: false,
+ showVersionDetails: true,
+ showBinaryPath: true,
+ installed: true,
+ installedVersion: '1.0.0',
+ binaryPath: '/usr/local/bin/agent-teams',
+ latestVersion: '1.0.0',
+ updateAvailable: false,
+ authLoggedIn: false,
+ authStatusChecking: false,
+ authMethod: null,
+ providers: [
+ {
+ providerId: 'anthropic',
+ displayName: 'Anthropic',
+ supported: true,
+ authenticated: true,
+ authMethod: 'oauth',
+ verificationState: 'verified',
+ statusMessage: 'Connected',
+ models: [],
+ canLoginFromUi: true,
+ 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 },
+ },
+ },
+ connection: null,
+ backend: null,
+ },
+ ],
+ ...overrides,
+ };
+}
+
describe('SkillsPanel', () => {
beforeEach(() => {
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true);
@@ -293,4 +339,38 @@ describe('SkillsPanel', () => {
await Promise.resolve();
});
});
+
+ it('uses a runtime-aware shared skills banner when codex is unavailable', async () => {
+ storeState.cliStatus = makeMultimodelStatus();
+ const host = document.createElement('div');
+ document.body.appendChild(host);
+ const root = createRoot(host);
+
+ await act(async () => {
+ root.render(
+ React.createElement(SkillsPanel, {
+ projectPath: '/tmp/project-a',
+ projectLabel: 'Project A',
+ skillsSearchQuery: '',
+ setSkillsSearchQuery: vi.fn(),
+ skillsSort: 'name-asc',
+ setSkillsSort: vi.fn(),
+ selectedSkillId: null,
+ setSelectedSkillId: vi.fn(),
+ })
+ );
+ await Promise.resolve();
+ await Promise.resolve();
+ });
+
+ expect(host.textContent).toContain(
+ 'Shared skills in `.claude`, `.cursor`, and `.agents` are available to Anthropic.'
+ );
+ expect(host.textContent).not.toContain('available to both Anthropic and Codex');
+
+ await act(async () => {
+ root.unmount();
+ await Promise.resolve();
+ });
+ });
});