fix(extensions): treat multimodel flavor as runtime-aware before hydration
This commit is contained in:
parent
3446ef0100
commit
7b16cfe73b
4 changed files with 73 additions and 49 deletions
|
|
@ -15,7 +15,7 @@ export function getVisibleMultimodelProviders(
|
||||||
export function isMultimodelRuntimeStatus(
|
export function isMultimodelRuntimeStatus(
|
||||||
cliStatus: Pick<CliInstallationStatus, 'flavor' | 'providers'> | null | undefined
|
cliStatus: Pick<CliInstallationStatus, 'flavor' | 'providers'> | null | undefined
|
||||||
): boolean {
|
): boolean {
|
||||||
return cliStatus?.flavor === 'agent_teams_orchestrator' && (cliStatus.providers?.length ?? 0) > 0;
|
return cliStatus?.flavor === 'agent_teams_orchestrator';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatCliExtensionCapabilityStatus(
|
export function formatCliExtensionCapabilityStatus(
|
||||||
|
|
|
||||||
|
|
@ -266,7 +266,7 @@ export function getExtensionActionDisableReason(options: {
|
||||||
}
|
}
|
||||||
|
|
||||||
const providers = cliStatus.providers ?? [];
|
const providers = cliStatus.providers ?? [];
|
||||||
const isMultimodel = cliStatus.flavor === 'agent_teams_orchestrator' && providers.length > 0;
|
const isMultimodel = cliStatus.flavor === 'agent_teams_orchestrator';
|
||||||
|
|
||||||
if (section === 'mcp') {
|
if (section === 'mcp') {
|
||||||
if (!isMultimodel) {
|
if (!isMultimodel) {
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,15 @@ describe('multimodelProviderVisibility', () => {
|
||||||
expect(getVisibleMultimodelProviders(cliStatus.providers)).toHaveLength(0);
|
expect(getVisibleMultimodelProviders(cliStatus.providers)).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('keeps multimodel runtime detection true even before provider metadata arrives', () => {
|
||||||
|
const cliStatus = {
|
||||||
|
flavor: 'agent_teams_orchestrator',
|
||||||
|
providers: [],
|
||||||
|
} satisfies Pick<CliInstallationStatus, 'flavor' | 'providers'>;
|
||||||
|
|
||||||
|
expect(isMultimodelRuntimeStatus(cliStatus)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it('filters Gemini from the visible provider cards while keeping supported providers', () => {
|
it('filters Gemini from the visible provider cards while keeping supported providers', () => {
|
||||||
const providers = [
|
const providers = [
|
||||||
createProvider('anthropic'),
|
createProvider('anthropic'),
|
||||||
|
|
|
||||||
|
|
@ -23,21 +23,15 @@ import {
|
||||||
|
|
||||||
describe('normalizeRepoUrl', () => {
|
describe('normalizeRepoUrl', () => {
|
||||||
it('lowercases and strips .git', () => {
|
it('lowercases and strips .git', () => {
|
||||||
expect(normalizeRepoUrl('https://GitHub.com/Org/Repo.git')).toBe(
|
expect(normalizeRepoUrl('https://GitHub.com/Org/Repo.git')).toBe('https://github.com/org/repo');
|
||||||
'https://github.com/org/repo',
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('strips trailing slashes', () => {
|
it('strips trailing slashes', () => {
|
||||||
expect(normalizeRepoUrl('https://github.com/org/repo/')).toBe(
|
expect(normalizeRepoUrl('https://github.com/org/repo/')).toBe('https://github.com/org/repo');
|
||||||
'https://github.com/org/repo',
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles already clean URLs', () => {
|
it('handles already clean URLs', () => {
|
||||||
expect(normalizeRepoUrl('https://github.com/org/repo')).toBe(
|
expect(normalizeRepoUrl('https://github.com/org/repo')).toBe('https://github.com/org/repo');
|
||||||
'https://github.com/org/repo',
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -68,9 +62,10 @@ describe('inferCapabilities', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('detects multiple capabilities', () => {
|
it('detects multiple capabilities', () => {
|
||||||
expect(
|
expect(inferCapabilities(makePlugin({ hasLspServers: true, hasMcpServers: true }))).toEqual([
|
||||||
inferCapabilities(makePlugin({ hasLspServers: true, hasMcpServers: true })),
|
'lsp',
|
||||||
).toEqual(['lsp', 'mcp']);
|
'mcp',
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('preserves capability order', () => {
|
it('preserves capability order', () => {
|
||||||
|
|
@ -80,8 +75,8 @@ describe('inferCapabilities', () => {
|
||||||
hasHooks: true,
|
hasHooks: true,
|
||||||
hasAgents: true,
|
hasAgents: true,
|
||||||
hasLspServers: true,
|
hasLspServers: true,
|
||||||
}),
|
})
|
||||||
),
|
)
|
||||||
).toEqual(['lsp', 'agent', 'hook']);
|
).toEqual(['lsp', 'agent', 'hook']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -151,7 +146,7 @@ describe('normalizeCategory', () => {
|
||||||
describe('buildPluginId', () => {
|
describe('buildPluginId', () => {
|
||||||
it('creates qualifiedName format', () => {
|
it('creates qualifiedName format', () => {
|
||||||
expect(buildPluginId('context7', 'claude-plugins-official')).toBe(
|
expect(buildPluginId('context7', 'claude-plugins-official')).toBe(
|
||||||
'context7@claude-plugins-official',
|
'context7@claude-plugins-official'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -159,36 +154,28 @@ describe('buildPluginId', () => {
|
||||||
describe('getPluginOperationKey', () => {
|
describe('getPluginOperationKey', () => {
|
||||||
it('namespaces user-scope plugin operation keys without a project suffix', () => {
|
it('namespaces user-scope plugin operation keys without a project suffix', () => {
|
||||||
expect(getPluginOperationKey('context7@claude-plugins-official', 'user')).toBe(
|
expect(getPluginOperationKey('context7@claude-plugins-official', 'user')).toBe(
|
||||||
'plugin:context7@claude-plugins-official:user',
|
'plugin:context7@claude-plugins-official:user'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('namespaces repo-scoped plugin operation keys by project path', () => {
|
it('namespaces repo-scoped plugin operation keys by project path', () => {
|
||||||
expect(
|
expect(getPluginOperationKey('context7@claude-plugins-official', 'local', '/tmp/project')).toBe(
|
||||||
getPluginOperationKey('context7@claude-plugins-official', 'local', '/tmp/project'),
|
'plugin:context7@claude-plugins-official:local:/tmp/project'
|
||||||
).toBe('plugin:context7@claude-plugins-official:local:/tmp/project');
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getMcpOperationKey', () => {
|
describe('getMcpOperationKey', () => {
|
||||||
it('namespaces MCP operation keys by scope', () => {
|
it('namespaces MCP operation keys by scope', () => {
|
||||||
expect(getMcpOperationKey('io.github.upstash/context7', 'project', '/tmp/project')).toBe(
|
expect(getMcpOperationKey('io.github.upstash/context7', 'project', '/tmp/project')).toBe(
|
||||||
'mcp:io.github.upstash/context7:project:/tmp/project',
|
'mcp:io.github.upstash/context7:project:/tmp/project'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('hasInstallationInScope', () => {
|
describe('hasInstallationInScope', () => {
|
||||||
it('returns true when the selected scope exists', () => {
|
it('returns true when the selected scope exists', () => {
|
||||||
expect(
|
expect(hasInstallationInScope([{ scope: 'user' }, { scope: 'project' }], 'project')).toBe(true);
|
||||||
hasInstallationInScope(
|
|
||||||
[
|
|
||||||
{ scope: 'user' },
|
|
||||||
{ scope: 'project' },
|
|
||||||
],
|
|
||||||
'project',
|
|
||||||
),
|
|
||||||
).toBe(true);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns false when the selected scope is missing', () => {
|
it('returns false when the selected scope is missing', () => {
|
||||||
|
|
@ -210,12 +197,9 @@ describe('getInstallationSummaryLabel', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('summarizes multiple scopes without pretending they are global', () => {
|
it('summarizes multiple scopes without pretending they are global', () => {
|
||||||
expect(
|
expect(getInstallationSummaryLabel([{ scope: 'project' }, { scope: 'user' }])).toBe(
|
||||||
getInstallationSummaryLabel([
|
'Installed in 2 scopes'
|
||||||
{ scope: 'project' },
|
);
|
||||||
{ scope: 'user' },
|
|
||||||
]),
|
|
||||||
).toBe('Installed in 2 scopes');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -252,12 +236,9 @@ describe('getMcpInstallationSummaryLabel', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('summarizes multiple MCP scopes', () => {
|
it('summarizes multiple MCP scopes', () => {
|
||||||
expect(
|
expect(getMcpInstallationSummaryLabel([{ scope: 'user' }, { scope: 'project' }])).toBe(
|
||||||
getMcpInstallationSummaryLabel([
|
'Installed in 2 scopes'
|
||||||
{ scope: 'user' },
|
);
|
||||||
{ scope: 'project' },
|
|
||||||
])
|
|
||||||
).toBe('Installed in 2 scopes');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -285,7 +266,7 @@ describe('getExtensionActionDisableReason', () => {
|
||||||
isInstalled: false,
|
isInstalled: false,
|
||||||
cliStatus: createDirectCliStatus({ authLoggedIn: false }),
|
cliStatus: createDirectCliStatus({ authLoggedIn: false }),
|
||||||
cliStatusLoading: false,
|
cliStatusLoading: false,
|
||||||
}),
|
})
|
||||||
).toContain('not signed in');
|
).toContain('not signed in');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -295,7 +276,7 @@ describe('getExtensionActionDisableReason', () => {
|
||||||
isInstalled: true,
|
isInstalled: true,
|
||||||
cliStatus: createDirectCliStatus({ authLoggedIn: false }),
|
cliStatus: createDirectCliStatus({ authLoggedIn: false }),
|
||||||
cliStatusLoading: false,
|
cliStatusLoading: false,
|
||||||
}),
|
})
|
||||||
).toBeNull();
|
).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -305,7 +286,7 @@ describe('getExtensionActionDisableReason', () => {
|
||||||
isInstalled: true,
|
isInstalled: true,
|
||||||
cliStatus: createDirectCliStatus({ installed: false, authLoggedIn: false }),
|
cliStatus: createDirectCliStatus({ installed: false, authLoggedIn: false }),
|
||||||
cliStatusLoading: false,
|
cliStatusLoading: false,
|
||||||
}),
|
})
|
||||||
).toContain('configured runtime');
|
).toContain('configured runtime');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -322,7 +303,7 @@ describe('getExtensionActionDisableReason', () => {
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
cliStatusLoading: false,
|
cliStatusLoading: false,
|
||||||
}),
|
})
|
||||||
).toContain('failed to start');
|
).toContain('failed to start');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -365,7 +346,7 @@ describe('getExtensionActionDisableReason', () => {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
cliStatusLoading: false,
|
cliStatusLoading: false,
|
||||||
}),
|
})
|
||||||
).toContain('Anthropic plugins unavailable');
|
).toContain('Anthropic plugins unavailable');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -404,9 +385,43 @@ describe('getExtensionActionDisableReason', () => {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
cliStatusLoading: false,
|
cliStatusLoading: false,
|
||||||
}),
|
})
|
||||||
).toBeNull();
|
).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('uses conservative multimodel fallback when provider metadata is not available yet', () => {
|
||||||
|
expect(
|
||||||
|
getExtensionActionDisableReason({
|
||||||
|
isInstalled: false,
|
||||||
|
section: 'plugins',
|
||||||
|
cliStatus: {
|
||||||
|
installed: true,
|
||||||
|
authLoggedIn: false,
|
||||||
|
binaryPath: '/usr/local/bin/claude-multimodel',
|
||||||
|
launchError: null,
|
||||||
|
flavor: 'agent_teams_orchestrator',
|
||||||
|
providers: [],
|
||||||
|
},
|
||||||
|
cliStatusLoading: false,
|
||||||
|
})
|
||||||
|
).toContain('not supported by the current runtime');
|
||||||
|
|
||||||
|
expect(
|
||||||
|
getExtensionActionDisableReason({
|
||||||
|
isInstalled: false,
|
||||||
|
section: 'mcp',
|
||||||
|
cliStatus: {
|
||||||
|
installed: true,
|
||||||
|
authLoggedIn: false,
|
||||||
|
binaryPath: '/usr/local/bin/claude-multimodel',
|
||||||
|
launchError: null,
|
||||||
|
flavor: 'agent_teams_orchestrator',
|
||||||
|
providers: [],
|
||||||
|
},
|
||||||
|
cliStatusLoading: false,
|
||||||
|
})
|
||||||
|
).toContain('not supported by the current runtime');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('sanitizeMcpServerName', () => {
|
describe('sanitizeMcpServerName', () => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue