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:
143
frontend/src/features/plugins/ProjectPluginsPanel.tsx
Normal file
143
frontend/src/features/plugins/ProjectPluginsPanel.tsx
Normal file
@ -0,0 +1,143 @@
|
||||
import { Panel, Spinner } from "@/shared";
|
||||
import { useProjectPlugins } from "./useProjectPlugins";
|
||||
|
||||
export interface ProjectPluginsPanelProps {
|
||||
projectId: string;
|
||||
}
|
||||
|
||||
function countLabel(count: number, singular: string, plural: string): string {
|
||||
return `${count} ${count === 1 ? singular : plural}`;
|
||||
}
|
||||
|
||||
export function ProjectPluginsPanel({ projectId }: ProjectPluginsPanelProps) {
|
||||
const vm = useProjectPlugins(projectId);
|
||||
|
||||
if (vm.loading && !vm.settings) {
|
||||
return (
|
||||
<Panel aria-label="plugins du projet" className="flex items-center gap-2">
|
||||
<Spinner size={14} />
|
||||
<span className="text-sm text-muted">Chargement des plugins du projet…</span>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
||||
const installedIds = new Set(vm.installed.map((plugin) => plugin.id));
|
||||
const missing = (vm.settings?.plugins ?? []).filter(
|
||||
(activation) => !installedIds.has(activation.pluginId),
|
||||
);
|
||||
|
||||
return (
|
||||
<section aria-label="plugins du projet" className="flex flex-col gap-3">
|
||||
<div>
|
||||
<h2 className="text-sm font-semibold text-content">Plugins du projet</h2>
|
||||
<p className="mt-1 text-xs text-muted">
|
||||
Un plugin reste installé globalement. Activez-le ici avant d’attribuer ses
|
||||
capabilities aux agents de ce projet.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{vm.error && <p role="alert" className="text-sm text-danger">{vm.error}</p>}
|
||||
|
||||
{vm.installed.length === 0 && missing.length === 0 ? (
|
||||
<Panel><p className="text-sm text-muted">Aucun plugin installé globalement.</p></Panel>
|
||||
) : (
|
||||
<ul className="flex flex-col gap-2">
|
||||
{vm.installed.map((plugin) => {
|
||||
const enabled = vm.settings?.plugins.some(
|
||||
(entry) => entry.pluginId === plugin.id && entry.enabled,
|
||||
) ?? false;
|
||||
const skills = plugin.contributes.skills ?? [];
|
||||
const servers = plugin.contributes.mcpServers ?? [];
|
||||
return (
|
||||
<li key={plugin.id}>
|
||||
<Panel className="flex flex-col gap-3">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="min-w-0">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="text-sm font-semibold text-content">{plugin.displayName}</span>
|
||||
<span className="text-xs text-faint">v{plugin.version}</span>
|
||||
<span className="rounded bg-raised px-1.5 py-0.5 text-[0.65rem] text-muted">
|
||||
Installé globalement
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-muted">
|
||||
{countLabel(skills.length, "skill", "skills")} · {countLabel(servers.length, "serveur MCP", "serveurs MCP")}
|
||||
</p>
|
||||
</div>
|
||||
<label className="flex shrink-0 items-center gap-2 text-xs text-content">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={enabled}
|
||||
disabled={vm.busy}
|
||||
onChange={(event) => void vm.setEnabled(plugin.id, event.target.checked)}
|
||||
/>
|
||||
Activé pour ce projet
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<details>
|
||||
<summary className="cursor-pointer text-xs font-medium text-primary">
|
||||
Voir les capabilities
|
||||
</summary>
|
||||
<div className="mt-3 grid gap-3 sm:grid-cols-2">
|
||||
<div>
|
||||
<h3 className="text-xs font-semibold text-content">Skills fournis</h3>
|
||||
{skills.length === 0 ? (
|
||||
<p className="mt-1 text-xs text-muted">Aucun skill fourni.</p>
|
||||
) : (
|
||||
<ul className="mt-1 flex flex-col gap-1">
|
||||
{skills.map((skill) => (
|
||||
<li key={skill.id} className="text-xs text-muted">
|
||||
<span className="font-medium text-content">{skill.name}</span>
|
||||
{skill.description ? ` — ${skill.description}` : ""}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-xs font-semibold text-content">Tools MCP fournis</h3>
|
||||
{servers.length === 0 ? (
|
||||
<p className="mt-1 text-xs text-muted">Aucun serveur MCP fourni.</p>
|
||||
) : (
|
||||
<ul className="mt-1 flex flex-col gap-1">
|
||||
{servers.map((server) => (
|
||||
<li key={server.id} className="text-xs text-muted">
|
||||
<span className="font-medium text-content">{server.displayName}</span>
|
||||
<span className="text-faint"> · {server.id}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</Panel>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
|
||||
{missing.map((activation) => (
|
||||
<li key={activation.pluginId}>
|
||||
<Panel className="flex items-center justify-between gap-4 border-warning/40">
|
||||
<div>
|
||||
<span className="text-sm font-semibold text-content">{activation.pluginId}</span>
|
||||
<p className="text-xs text-warning">Plugin manquant · configuration conservée</p>
|
||||
</div>
|
||||
<label className="flex items-center gap-2 text-xs text-muted">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={activation.enabled}
|
||||
disabled={vm.busy || !activation.enabled}
|
||||
onChange={(event) => void vm.setEnabled(activation.pluginId, event.target.checked)}
|
||||
/>
|
||||
Activé pour ce projet
|
||||
</label>
|
||||
</Panel>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user