fix(plugins): make official plugins project-wide

This commit is contained in:
2026-09-05 13:19:13 +08:00
parent 5e0a091955
commit e0de7aa28c
6 changed files with 121 additions and 17 deletions

View File

@@ -4,7 +4,7 @@ import path from 'node:path';
import { closeElectronApp, expect, getStableWindow, test } from './fixtures/electron';
test.describe('Unified project plugin workspace', () => {
test('enables and disables a project plugin without configuring it, then opens Agent assignment', async ({ launchElectronApp }) => {
test('enables and disables a project-wide official plugin without configuring or assigning it', async ({ launchElectronApp }) => {
const projectPath = await mkdtemp(path.join(tmpdir(), 'makelore-project-plugins-e2e-'));
const app = await launchElectronApp({ skipSetup: true });
try {
@@ -94,6 +94,9 @@ test.describe('Unified project plugin workspace', () => {
await expect(page).toHaveURL(/\/plugins\?scope=project/);
await page.getByRole('button', { name: '查看开发数据服务详情' }).click();
await expect(page.getByText('按平台包含,不按单次插件调用扣点。')).toBeVisible();
await expect(page.getByRole('heading', { name: '生效范围' })).toBeVisible();
await expect(page.getByText(/启用当前项目后.*无需单独分配/)).toBeVisible();
await expect(page.getByRole('button', { name: '分配开发数据服务给伙伴' })).toHaveCount(0);
const localProjectId = await app.evaluate(() => (
(globalThis as typeof globalThis & { __projectPluginE2E?: { localProjectId: string } }).__projectPluginE2E?.localProjectId
));
@@ -111,8 +114,6 @@ test.describe('Unified project plugin workspace', () => {
await page.getByRole('button', { name: '确认从当前项目禁用' }).click();
await expect(page.getByRole('button', { name: '启用开发数据服务到当前项目' })).toBeVisible();
await page.getByRole('button', { name: '分配开发数据服务给伙伴' }).click();
await expect(page.getByTestId('project-configuration-page')).toBeVisible();
const state = await app.evaluate(() => (
(globalThis as typeof globalThis & {
__projectPluginE2E?: { dataServiceCalls: number; mutations: Array<{ enabled: boolean; projectId: string }> };

View File

@@ -7,6 +7,8 @@ import {
} from '../../electron/coding-plugins/effective-resolver';
import {
DATA_SERVICE_PLUGIN_DEFINITION,
GAME_RESOURCE_BUNDLED_RELEASE_ID,
GAME_RESOURCE_PLUGIN_ID,
PROJECT_SCAFFOLD_BUNDLED_RELEASE_ID,
PROJECT_SCAFFOLD_PLUGIN_ID,
type CodingPluginDefinition,
@@ -450,11 +452,11 @@ describe('effective plugin resolver', () => {
expect(refresh).not.toHaveBeenCalled();
});
it('materializes an acquired code-owned bundled hosted Plugin without Package Store bytes', async () => {
it('materializes an acquired code-owned bundled hosted Plugin without Package Store bytes or Agent assignment', async () => {
const bundled: CodingPluginDefinition = {
...serverDefinition,
id: 'makelore.game-resource',
releaseId: '00000000-0000-4000-8000-000000000105',
id: GAME_RESOURCE_PLUGIN_ID,
releaseId: GAME_RESOURCE_BUNDLED_RELEASE_ID,
provenance: { source: 'bundled', packageRoot: 'game-resource' },
skills: [{
id: 'game-resource',
@@ -485,7 +487,7 @@ describe('effective plugin resolver', () => {
await expect(effective.resolve({
projectId: 'project-a',
projectPath: 'C:/project-a',
assignedSkillIds: ['game-resource'],
assignedSkillIds: [],
role: 'parent',
})).resolves.toMatchObject({
pluginReleaseIds: [bundled.releaseId],
@@ -504,6 +506,38 @@ describe('effective plugin resolver', () => {
expect(getInstalledRelease).not.toHaveBeenCalled();
});
it('materializes the system-included Data Service after project enablement without Agent assignment', async () => {
const policy = currentPolicy();
policy.catalog.plugins[0] = {
...policy.catalog.plugins[0],
plugin_id: DATA_SERVICE_PLUGIN_DEFINITION.id,
capabilities: DATA_SERVICE_PLUGIN_DEFINITION.operations.map(({ capabilityId, operation }) => ({
capability_id: capabilityId,
operations: [{
operation,
billing: { mode: 'included' as const, entitlement_scope: null, notice: 'Included' },
}],
})),
};
const effective = createEffectivePluginResolver({
definitions: [DATA_SERVICE_PLUGIN_DEFINITION],
getAccountBinding: () => binding,
getEnabledPluginIds: vi.fn(async () => [DATA_SERVICE_PLUGIN_DEFINITION.id]),
policyClient: { getState: () => policy, refresh: vi.fn() },
});
await expect(effective.resolve({
projectId: 'project-a',
projectPath: 'C:/project-a',
assignedSkillIds: [],
role: 'parent',
})).resolves.toMatchObject({
effectiveSkillIds: ['data-service'],
skillEntries: [{ id: 'data-service', entryPath: 'skills/data-service/SKILL.md' }],
unavailableReasons: [],
});
});
it('materializes the project-wide Scaffold Skill after project enablement without Agent assignment', async () => {
const scaffoldLibrary = {
...library(),

View File

@@ -257,7 +257,7 @@ describe('buildPluginWorkspaceProjection', () => {
channel: 'stable',
},
},
})).toEqual(['disable_project', 'open_agent_assignment', 'open_settings']);
})).toEqual(['disable_project', 'open_settings']);
});
it('keeps bundled and downloadable official command sets inside their delivery boundaries', () => {
@@ -298,9 +298,7 @@ describe('buildPluginWorkspaceProjection', () => {
library: { ...library, items: [bundledLibrary] },
marketplaceInstallations: {},
project: bundledProject,
})).toEqual(pluginId === 'makelore.project-scaffold'
? ['remove_from_library', 'enable_project']
: ['remove_from_library', 'enable_project', 'open_agent_assignment']);
})).toEqual(['remove_from_library', 'enable_project']);
}
expect(commandKinds({ marketplaceInstallations: {} })).toEqual([

View File

@@ -89,7 +89,6 @@ const officialItem: PluginWorkspaceItem = {
deviceReason: null,
commands: [
{ kind: 'disable_project', projectId: 'project-a', pluginId: 'makelore.data-service' },
{ kind: 'open_agent_assignment', projectId: 'project-a', pluginId: 'makelore.data-service' },
{ kind: 'open_settings', projectId: 'project-a', pluginId: 'makelore.data-service' },
],
official: {
@@ -433,7 +432,7 @@ describe('PluginsView', () => {
expect(screen.getByRole('heading', { name: '本机全局生效' })).toBeVisible();
expect(screen.getByText('本机全局已启用')).toBeVisible();
expect(screen.getByText('随 MakeLore 提供')).toBeVisible();
expect(screen.getByText('已分配 1 位伙伴')).toBeVisible();
expect(screen.getByText('随项目启用')).toBeVisible();
expect(screen.getAllByRole('alert')).toHaveLength(2);
expect(screen.queryByRole('textbox', { name: /包|路径|source/i })).not.toBeInTheDocument();
expect(screen.getByText(/通过对话安装/)).toBeVisible();
@@ -501,7 +500,10 @@ describe('PluginsView', () => {
expect(dialog).toHaveTextContent('读取项目名称');
expect(dialog).toHaveTextContent('data-service.documents');
expect(dialog).toHaveTextContent('按平台包含');
expect(dialog).toHaveTextContent('小明');
expect(dialog).toHaveTextContent('生效范围');
expect(dialog).toHaveTextContent('无需单独分配');
expect(dialog).not.toHaveTextContent('小明');
expect(screen.queryByRole('button', { name: '分配开发数据服务给伙伴' })).not.toBeInTheDocument();
expect(dialog).toHaveTextContent('插件专属设置');
expect(dialog).toHaveTextContent('创建开发数据空间');
@@ -727,9 +729,9 @@ describe('PluginsView', () => {
const view = props(officialItem);
render(<MemoryRouter><PluginsView {...view} /></MemoryRouter>);
fireEvent.click(screen.getByRole('button', { name: '分配开发数据服务给伙伴' }));
fireEvent.click(screen.getByRole('button', { name: '查看开发数据服务插件设置' }));
expect(view.onCommand).toHaveBeenCalledWith({
kind: 'open_agent_assignment', projectId: 'project-a', pluginId: 'makelore.data-service',
kind: 'open_settings', projectId: 'project-a', pluginId: 'makelore.data-service',
});
fireEvent.click(screen.getByRole('button', { name: '关闭插件详情' }));
expect(view.onSelect).toHaveBeenCalledWith(null);