fix(application,domain,infrastructure,web-server,app-tauri,frontend): arrêt d'agent fiable avec cascade sur l'arbre de propriété et statut cohérent — #320 (QA verte)
Symptôme: stopper un agent (ex Main sur UnityPlugin) laissait des travaux poursuivre, la cascade n'atteignait pas les agents lancés et la pastille restait orange pendant que l'agent continuait à en appeler d'autres. - barrière d'admission par génération d'exécution (ExecutionControl): un stop ferme l'admission de tout le sous-arbre propriété/attente AVANT toute annulation asynchrone — plus aucune nouvelle action inter-agents après stop - arbre de propriété durable en-process (launched_by) + dépendances d'attente actives: la cascade couvre transitivement agents et travaux lancés - commandes d'un agent arrêté rejetées (AppError::AgentStopped); stop lui-même admis pour rester idempotent; réactivation sur relance humaine (génération +1) et enregistrement du propriétaire au spawn - admission exposée aux adaptateurs hors commandes cœur (tickets, templates, plugins) via ensure_agent_execution_active; MCP server/tools, web-server et commandes Tauri alignés - cohérence live-state: statut Idle avec intent « Arrêté » ou « Arrêt incomplet — Certains travaux sont encore actifs » selon l'issue de la cascade; workstate et surfaces UI (AgentsPanel, LayoutGrid, ProjectWorkStatePanel) reflètent l'état réel, plus de pastille orange fantôme - tests: orchestrator_service (cascade/générations/idempotence), workstate_actions, background_task_cancel, error_codes, agents, layout, workstate QA verte sur fix/320-agent-stop-cascade. Aucun push.
This commit is contained in:
@ -85,6 +85,9 @@ export function AgentsPanel({ projectId, projectRoot = "" }: AgentsPanelProps) {
|
||||
* set by the hook once launchAgent resolves.
|
||||
*/
|
||||
const [activeAgentId, setActiveAgentId] = useState<string | null>(null);
|
||||
const [stopStateByAgent, setStopStateByAgent] = useState<
|
||||
Record<string, "stopping" | "incomplete">
|
||||
>({});
|
||||
const [panelNodeIds, setPanelNodeIds] = useState<Record<string, string>>({});
|
||||
|
||||
/**
|
||||
@ -228,9 +231,19 @@ export function AgentsPanel({ projectId, projectRoot = "" }: AgentsPanelProps) {
|
||||
setActiveAgentId(agentId);
|
||||
}
|
||||
|
||||
function handleStop(agentId?: string) {
|
||||
void vm.stopAgent(agentId);
|
||||
setActiveAgentId(null);
|
||||
async function handleStop(agentId: string) {
|
||||
setStopStateByAgent((current) => ({ ...current, [agentId]: "stopping" }));
|
||||
const stopped = await vm.stopAgent(agentId);
|
||||
if (stopped) {
|
||||
setActiveAgentId((current) => (current === agentId ? null : current));
|
||||
setStopStateByAgent((current) => {
|
||||
const next = { ...current };
|
||||
delete next[agentId];
|
||||
return next;
|
||||
});
|
||||
} else {
|
||||
setStopStateByAgent((current) => ({ ...current, [agentId]: "incomplete" }));
|
||||
}
|
||||
}
|
||||
|
||||
// Determine if a template is chosen → profile selector is hidden (template imposes it).
|
||||
@ -386,6 +399,7 @@ export function AgentsPanel({ projectId, projectRoot = "" }: AgentsPanelProps) {
|
||||
{vm.agents.map((a) => {
|
||||
const isSelected = a.id === vm.selectedAgentId;
|
||||
const isRunning = a.id === activeAgentId;
|
||||
const stopState = stopStateByAgent[a.id];
|
||||
const live = vm.liveAgents.find((candidate) => candidate.agentId === a.id);
|
||||
const agentProfile = vm.profiles.find((p) => p.id === a.profileId) ?? null;
|
||||
const profileName =
|
||||
@ -561,11 +575,22 @@ export function AgentsPanel({ projectId, projectRoot = "" }: AgentsPanelProps) {
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
aria-label={`stop ${a.name}`}
|
||||
onClick={() => handleStop(a.id)}
|
||||
aria-label={
|
||||
stopState === "stopping"
|
||||
? `Arrêt en cours… — ${a.name}`
|
||||
: stopState === "incomplete"
|
||||
? `Réessayer l’arrêt — ${a.name}`
|
||||
: `stop ${a.name}`
|
||||
}
|
||||
disabled={stopState === "stopping"}
|
||||
onClick={() => void handleStop(a.id)}
|
||||
className="text-danger hover:text-danger"
|
||||
>
|
||||
Stop
|
||||
{stopState === "stopping"
|
||||
? "Arrêt en cours…"
|
||||
: stopState === "incomplete"
|
||||
? "Réessayer l’arrêt"
|
||||
: "Stop"}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
|
||||
Reference in New Issue
Block a user