feat(plugins): paramètres projet plugins + attribution skills/tools aux agents — #282 (manifeste accepte contributes.skills IdeA-native multi-harness; store .ideai/project-plugins.json v1: enable plugin par projet + assignments skills/tools par agent; refs + use cases settings/assign/unassign; resolver skills pour idea_skill_read et injection catalogue lancement agent; permissions MCP dynamiques projetées par harness Claude Code/Codex/OpenCode; tombstones uninstall, hot reload recalcule catalogue et marque assignments cassés; frontend: Settings projet Plugins, détail capabilities, attribution agent, catalogue permissions; bump pointeur IdeaSDK 6eb8db3; QA verte: cargo test -p application, project_plugin_settings 3/3, dto_plugins 2/2, structured_launch_d3 26/26, catalogue mcp 7/7, frontend 5 tests ProjectPluginSettings/plugin, npm build frontend)
This commit is contained in:
@ -57,6 +57,9 @@ import type {
|
||||
PluginProjectStructureEntry,
|
||||
PluginReview,
|
||||
PluginRuntimeContributionCatalog,
|
||||
ProjectPluginManifest,
|
||||
ProjectPluginSettings,
|
||||
ProjectPluginSettingsView,
|
||||
PluginToolchainDiagnostic,
|
||||
PluginUninstallResult,
|
||||
PluginPublicEvent,
|
||||
@ -138,6 +141,8 @@ import type {
|
||||
PluginEventSubscribeInput,
|
||||
PluginEventUnsubscribeInput,
|
||||
PluginGateway,
|
||||
MutateAgentPluginSkillInput,
|
||||
MutateAgentPluginToolInput,
|
||||
PluginStorageGateway,
|
||||
PluginStorageGetInput,
|
||||
PluginStorageSetInput,
|
||||
@ -155,6 +160,7 @@ import type {
|
||||
ReattachAgentChatResult,
|
||||
RemoteGateway,
|
||||
ReviewPluginPackageInput,
|
||||
SetProjectPluginEnabledInput,
|
||||
SaveOpenCodeProviderProfileInput,
|
||||
SendAgentChatOptions,
|
||||
SkillGateway,
|
||||
@ -4004,6 +4010,8 @@ function isBlanketRule(rule: PermissionSet["rules"][number]) {
|
||||
*/
|
||||
export class MockPluginGateway implements PluginGateway {
|
||||
private plugins: PluginAdmin[] = [];
|
||||
private projectManifests: ProjectPluginManifest[] = [];
|
||||
private projectSettings = new Map<string, ProjectPluginSettings>();
|
||||
private seq = 0;
|
||||
|
||||
private summaryFor(_pluginId: string): PluginContributionSummary {
|
||||
@ -4015,6 +4023,24 @@ export class MockPluginGateway implements PluginGateway {
|
||||
this.plugins.push(plugin);
|
||||
}
|
||||
|
||||
/** Test/dev helper: seed a full manifest for project capability settings. */
|
||||
_seedProjectPluginManifest(manifest: ProjectPluginManifest): void {
|
||||
this.projectManifests.push(structuredClone(manifest));
|
||||
}
|
||||
|
||||
/** Test/dev helper: seed activation/assignment tombstones. */
|
||||
_seedProjectPluginSettings(projectId: string, settings: ProjectPluginSettings): void {
|
||||
this.projectSettings.set(projectId, structuredClone(settings));
|
||||
}
|
||||
|
||||
private settingsFor(projectId: string): ProjectPluginSettings {
|
||||
const existing = this.projectSettings.get(projectId);
|
||||
if (existing) return existing;
|
||||
const created: ProjectPluginSettings = { version: 1, plugins: [], agents: [] };
|
||||
this.projectSettings.set(projectId, created);
|
||||
return created;
|
||||
}
|
||||
|
||||
async listPlugins(): Promise<PluginAdmin[]> {
|
||||
return structuredClone(this.plugins);
|
||||
}
|
||||
@ -4099,6 +4125,68 @@ export class MockPluginGateway implements PluginGateway {
|
||||
return { plugins: [] };
|
||||
}
|
||||
|
||||
async listProjectSettings(projectId: string): Promise<ProjectPluginSettingsView> {
|
||||
return {
|
||||
settings: structuredClone(this.settingsFor(projectId)),
|
||||
installed: structuredClone(this.projectManifests),
|
||||
};
|
||||
}
|
||||
|
||||
async setProjectEnabled(input: SetProjectPluginEnabledInput): Promise<ProjectPluginSettings> {
|
||||
if (input.enabled && !this.projectManifests.some((plugin) => plugin.id === input.pluginId)) {
|
||||
throw { code: "NOT_FOUND", message: `plugin ${input.pluginId} not found` } satisfies GatewayError;
|
||||
}
|
||||
const settings = this.settingsFor(input.projectId);
|
||||
const activation = settings.plugins.find((entry) => entry.pluginId === input.pluginId);
|
||||
if (activation) activation.enabled = input.enabled;
|
||||
else settings.plugins.push({ pluginId: input.pluginId, enabled: input.enabled });
|
||||
return structuredClone(settings);
|
||||
}
|
||||
|
||||
private assignmentsFor(projectId: string, agentId: string) {
|
||||
const settings = this.settingsFor(projectId);
|
||||
let assignments = settings.agents.find((entry) => entry.agentId === agentId);
|
||||
if (!assignments) {
|
||||
assignments = { agentId, skills: [], tools: [] };
|
||||
settings.agents.push(assignments);
|
||||
}
|
||||
return assignments;
|
||||
}
|
||||
|
||||
async assignAgentSkill(input: MutateAgentPluginSkillInput): Promise<ProjectPluginSettings> {
|
||||
const assignments = this.assignmentsFor(input.projectId, input.agentId);
|
||||
if (!assignments.skills.some((ref) => ref.pluginId === input.pluginId && ref.skillId === input.skillId)) {
|
||||
assignments.skills.push({ pluginId: input.pluginId, skillId: input.skillId });
|
||||
}
|
||||
return structuredClone(this.settingsFor(input.projectId));
|
||||
}
|
||||
|
||||
async unassignAgentSkill(input: MutateAgentPluginSkillInput): Promise<ProjectPluginSettings> {
|
||||
const assignments = this.assignmentsFor(input.projectId, input.agentId);
|
||||
assignments.skills = assignments.skills.filter(
|
||||
(ref) => ref.pluginId !== input.pluginId || ref.skillId !== input.skillId,
|
||||
);
|
||||
return structuredClone(this.settingsFor(input.projectId));
|
||||
}
|
||||
|
||||
async assignAgentTool(input: MutateAgentPluginToolInput): Promise<ProjectPluginSettings> {
|
||||
const assignments = this.assignmentsFor(input.projectId, input.agentId);
|
||||
if (!assignments.tools.some((ref) =>
|
||||
ref.pluginId === input.pluginId && ref.serverId === input.serverId && ref.toolName === input.toolName
|
||||
)) {
|
||||
assignments.tools.push({ pluginId: input.pluginId, serverId: input.serverId, toolName: input.toolName });
|
||||
}
|
||||
return structuredClone(this.settingsFor(input.projectId));
|
||||
}
|
||||
|
||||
async unassignAgentTool(input: MutateAgentPluginToolInput): Promise<ProjectPluginSettings> {
|
||||
const assignments = this.assignmentsFor(input.projectId, input.agentId);
|
||||
assignments.tools = assignments.tools.filter((ref) =>
|
||||
ref.pluginId !== input.pluginId || ref.serverId !== input.serverId || ref.toolName !== input.toolName
|
||||
);
|
||||
return structuredClone(this.settingsFor(input.projectId));
|
||||
}
|
||||
|
||||
async openPluginsFolder(_pluginId?: string): Promise<void> {
|
||||
// No filesystem in the mock — no-op.
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user