feat(backend,application,frontend,domain,web-server,app-tauri): bouton Redémarrer l'agent dans la barre supérieure des cellules — #317 (QA verte)

La cellule expose un bouton Redémarrer l'agent valable pour les trois
modes de présentation (headless, TUI native, CLI custom), sans jamais
prendre la présentation pour vérité runtime.

- Application: use case RestartCellAgentRuntime validant le binding
  exact cellule/runtime via les snapshots live (RestartRuntimeNotFound,
  ConflictAgentVisibleElsewhere, AgentNotBoundToCell) puis déléguant au
  scheduler self-restart existant (schedule_cell); CellAgentPresentation
  purement informationnelle.
- Backend/app-tauri/web-server: commande restart_cell_agent sur contrat
  DTO identique Tauri/HTTP, stop borné par timeout typé
  (RestartStopTimeout), relance préservant routage TUI natif, géométrie
  de cellule (node/rows/cols) et conversation; évenements domaine
  enrichis.
- Frontend: bouton dans la barre de cellule avec confirmation,
  overlay d'état Redémarrage…/échec (libellés par code d'erreur, Fermer/
  Réessayer), gardes d'identité de cellule et de restarts externes,
  gateway/mock/http adapters et ports alignés.
- Tests: dto_agents, workstate_actions, contrats web-server,
  LayoutGrid.cellControlsLayering, adapters agent/mock/http.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-09-11 15:00:43 +02:00
parent 46449bdf46
commit 7fb9d8bb86
26 changed files with 1404 additions and 58 deletions

View File

@ -164,6 +164,8 @@ import type {
PluginWorkspaceWriteTextInput,
ReattachResult,
ReattachAgentChatResult,
RestartCellAgentRuntimeInput,
RestartCellAgentRuntimeScheduled,
RemoteGateway,
ReviewPluginPackageInput,
SetProjectPluginEnabledInput,
@ -406,6 +408,7 @@ export class MockAgentGateway implements AgentGateway {
private contexts = new Map<string, string>();
/** Monotonic session counter for deterministic session ids in tests. */
private sessionSeq = 0;
private restartSeq = 0;
/** Live agent PTY sessions, kept across detach so reattach can find them. */
private sessions = new Map<string, MockPtySession>();
/**
@ -568,6 +571,37 @@ export class MockAgentGateway implements AgentGateway {
return stopped;
}
async restartCellAgentRuntime(
input: RestartCellAgentRuntimeInput,
): Promise<RestartCellAgentRuntimeScheduled> {
const known = this.getAgents(input.projectId).some(
(agent) => agent.id === input.agentId,
);
const sessionId = this.liveSessionByAgent.get(input.agentId);
if (!known || !sessionId) {
throw {
code: "RUNTIME_NOT_FOUND",
message: `agent ${input.agentId} has no live runtime`,
} as GatewayError;
}
if (this.liveByAgent.get(input.agentId) !== input.nodeId) {
throw {
code: "AGENT_NOT_BOUND_TO_CELL",
message: `agent ${input.agentId} is not bound to cell ${input.nodeId}`,
} as GatewayError;
}
this.restartSeq += 1;
return {
restartId: `mock-restart-${this.restartSeq}`,
status: "scheduled",
agentId: input.agentId,
nodeId: input.nodeId,
kind: this.liveKindByAgent.get(input.agentId) ?? "pty",
presentation: input.presentation,
layoutId: input.layoutId ?? null,
};
}
async createAgent(projectId: string, input: CreateAgentInput): Promise<Agent> {
const list = this.getAgents(projectId);
const slug = slugify(input.name) || "agent";