feat(agents): role orchestrateur du contexte global expose et configurable par agent — isOrchestrator distinct des permissions MCP, DTO update_project_orchestrator (terminologie wire legacy conservee), refus idea_context_update explicite avec writer effectif + orientation idea_context_propose, surface AgentsPanel + adaptateurs frontend — #276 (QA verte: cargo fmt/check, tests cibles application/app-tauri/infrastructure/backend, npm typecheck, 69/69 tests frontend agents/adapters)
This commit is contained in:
@ -188,6 +188,11 @@ export function AgentsPanel({ projectId, projectRoot = "" }: AgentsPanelProps) {
|
||||
agentId: string;
|
||||
profileId: string;
|
||||
} | null>(null);
|
||||
const [pendingWriterChange, setPendingWriterChange] = useState<{
|
||||
agentId: string;
|
||||
agentName: string;
|
||||
currentWriterName: string;
|
||||
} | null>(null);
|
||||
|
||||
async function confirmProfileChange() {
|
||||
if (!pendingProfileChange) return;
|
||||
@ -204,6 +209,27 @@ export function AgentsPanel({ projectId, projectRoot = "" }: AgentsPanelProps) {
|
||||
}
|
||||
}
|
||||
|
||||
function requestWriterChange(agent: Agent) {
|
||||
if (agent.isOrchestrator || vm.busy) return;
|
||||
const currentWriter = vm.agents.find((candidate) => candidate.isOrchestrator);
|
||||
if (currentWriter && currentWriter.id !== agent.id) {
|
||||
setPendingWriterChange({
|
||||
agentId: agent.id,
|
||||
agentName: agent.name,
|
||||
currentWriterName: currentWriter.name,
|
||||
});
|
||||
return;
|
||||
}
|
||||
void vm.updateProjectOrchestrator(agent.id);
|
||||
}
|
||||
|
||||
async function confirmWriterChange() {
|
||||
if (!pendingWriterChange) return;
|
||||
const agentId = pendingWriterChange.agentId;
|
||||
setPendingWriterChange(null);
|
||||
await vm.updateProjectOrchestrator(agentId);
|
||||
}
|
||||
|
||||
const selectedTemplate = templates.find((t) => t.id === newTemplateId) ?? null;
|
||||
const selectedTemplateMissingProfile =
|
||||
selectedTemplate !== null && selectedTemplate.defaultProfileId.trim().length === 0;
|
||||
@ -440,6 +466,14 @@ export function AgentsPanel({ projectId, projectRoot = "" }: AgentsPanelProps) {
|
||||
>
|
||||
<span className="flex min-w-0 max-w-full flex-wrap items-center gap-2">
|
||||
<span className="font-medium text-content">{a.name}</span>
|
||||
{a.isOrchestrator && (
|
||||
<span
|
||||
aria-label="Writer contexte"
|
||||
className="rounded-full border border-border-strong bg-raised px-2 py-0.5 text-xs font-medium text-content"
|
||||
>
|
||||
Writer contexte
|
||||
</span>
|
||||
)}
|
||||
{agentDrift && (
|
||||
<span
|
||||
aria-label="update available"
|
||||
@ -471,6 +505,35 @@ export function AgentsPanel({ projectId, projectRoot = "" }: AgentsPanelProps) {
|
||||
)}
|
||||
</button>
|
||||
|
||||
{isSelected && (
|
||||
<section
|
||||
aria-label={`Rôle dans le projet — ${a.name}`}
|
||||
className="rounded-md border border-border bg-surface p-3"
|
||||
>
|
||||
<h4 className="text-xs font-semibold uppercase tracking-wide text-muted">
|
||||
Rôle dans le projet
|
||||
</h4>
|
||||
<label className="mt-2 flex cursor-pointer items-start gap-2">
|
||||
<input
|
||||
type="radio"
|
||||
name="project-context-writer"
|
||||
aria-label={`Writer du contexte projet global pour ${a.name}`}
|
||||
checked={a.isOrchestrator === true}
|
||||
disabled={vm.busy}
|
||||
onChange={() => requestWriterChange(a)}
|
||||
className="mt-0.5"
|
||||
/>
|
||||
<span className="text-sm font-medium text-content">
|
||||
Writer du contexte projet global
|
||||
</span>
|
||||
</label>
|
||||
<p className="mt-1 pl-5 text-xs text-muted">
|
||||
Peut modifier directement le contexte partagé du projet.
|
||||
Un seul agent peut avoir ce rôle.
|
||||
</p>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{limitState && (
|
||||
<AgentLimitBadge
|
||||
state={limitState}
|
||||
@ -747,6 +810,44 @@ export function AgentsPanel({ projectId, projectRoot = "" }: AgentsPanelProps) {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{pendingWriterChange && (
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="Confirmer le changement de writer contexte"
|
||||
className="fixed inset-0 z-50 flex items-center justify-center bg-black/55 p-4"
|
||||
>
|
||||
<div className="w-full max-w-sm rounded-lg border border-border bg-surface p-5 shadow-xl">
|
||||
<h4 className="text-sm font-semibold text-content">
|
||||
Changer le writer du contexte
|
||||
</h4>
|
||||
<p className="mt-2 text-sm text-muted">
|
||||
Le rôle sera retiré de {pendingWriterChange.currentWriterName} et
|
||||
attribué à {pendingWriterChange.agentName}. Continuer ?
|
||||
</p>
|
||||
<div className="mt-4 flex items-center justify-end gap-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
aria-label="Annuler le changement de writer contexte"
|
||||
disabled={vm.busy}
|
||||
onClick={() => setPendingWriterChange(null)}
|
||||
>
|
||||
Annuler
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
aria-label="Confirmer le changement de writer contexte"
|
||||
loading={vm.busy}
|
||||
disabled={vm.busy}
|
||||
onClick={() => void confirmWriterChange()}
|
||||
>
|
||||
Continuer
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user