From f0ba5598854bbb4ed7119dac4a766e2f9404d4bf Mon Sep 17 00:00:00 2001 From: Blomios Date: Tue, 28 Jul 2026 22:51:48 +0200 Subject: [PATCH] fix(frontend): expose idea_ask_agents et renforce le fit terminal au remount projet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Panneau Permissions: idea_ask_agents est câblé côté backend MCP mais absent du groupe « Délégation agents » du panneau — ajout de l'outil et de son label, catalogue mock MCP aligné en conséquence. Terminaux: ProjectsView remonte LayoutGrid via une key au changement de projet, donc refitEpoch n'aide pas ; le montage de TerminalView ne maintenait pas assez longtemps le fit tant que la nouvelle cellule ne s'était pas stabilisée. Le chemin de montage garde désormais une traîne de refits plus longue (MOUNT_SETTLE_REFIT_FRAMES) avant de laisser la main au ResizeObserver. Co-Authored-By: Claude Opus 4.8 --- frontend/src/adapters/mock/index.ts | 1 + .../McpToolPermissionsPanel.test.tsx | 13 ++++++ .../src/features/permissions/mcpToolGroups.ts | 8 +++- .../features/terminals/TerminalView.test.tsx | 46 +++++++++++++++++++ .../src/features/terminals/TerminalView.tsx | 8 +++- 5 files changed, 73 insertions(+), 3 deletions(-) diff --git a/frontend/src/adapters/mock/index.ts b/frontend/src/adapters/mock/index.ts index 6702c38..8de814d 100644 --- a/frontend/src/adapters/mock/index.ts +++ b/frontend/src/adapters/mock/index.ts @@ -2462,6 +2462,7 @@ export class MockPermissionGateway implements PermissionGateway { ], writeActionTools: [ "idea_ask_agent", + "idea_ask_agents", "idea_run_in_background", "idea_launch_agent", "idea_stop_agent", diff --git a/frontend/src/features/permissions/McpToolPermissionsPanel.test.tsx b/frontend/src/features/permissions/McpToolPermissionsPanel.test.tsx index 9b0e3d5..33cd6d6 100644 --- a/frontend/src/features/permissions/McpToolPermissionsPanel.test.tsx +++ b/frontend/src/features/permissions/McpToolPermissionsPanel.test.tsx @@ -108,6 +108,19 @@ describe("PermissionsPanel — Tools MCP IdeA tab", () => { expect(screen.getByRole("button", { name: "Enregistrer" })).toHaveProperty("disabled", true); }); + it("shows multi-agent delegation in the Délégation agents MCP group", async () => { + await renderPanel(); + await openMcpToolsTab(); + + const delegationGroup = screen.getByRole("button", { + name: /Délégation agents/, + }); + expect(delegationGroup).toBeTruthy(); + expect(screen.getByRole("checkbox", { + name: /Solliciter plusieurs agents, idea_ask_agents$/, + })).toBeTruthy(); + }); + it("creating an override prefills the draft with the current effective allowlist", async () => { await renderPanel(); await openMcpToolsTab(); diff --git a/frontend/src/features/permissions/mcpToolGroups.ts b/frontend/src/features/permissions/mcpToolGroups.ts index 82c3606..603e031 100644 --- a/frontend/src/features/permissions/mcpToolGroups.ts +++ b/frontend/src/features/permissions/mcpToolGroups.ts @@ -39,7 +39,12 @@ export const MCP_TOOL_GROUPS: McpToolGroupDef[] = [ { id: "delegation", label: "Délégation agents", - tools: ["idea_ask_agent", "idea_launch_agent", "idea_stop_agent"], + tools: [ + "idea_ask_agent", + "idea_ask_agents", + "idea_launch_agent", + "idea_stop_agent", + ], }, { id: "contextMemory", @@ -83,6 +88,7 @@ export const MCP_TOOL_LABELS: Record = { idea_ticket_read_carnet: "Lire le carnet d'un ticket", idea_sprint_list: "Lister les sprints", idea_ask_agent: "Déléguer à un agent", + idea_ask_agents: "Solliciter plusieurs agents", idea_launch_agent: "Lancer un agent", idea_stop_agent: "Arrêter un agent", idea_update_context: "Modifier le contexte d'un agent", diff --git a/frontend/src/features/terminals/TerminalView.test.tsx b/frontend/src/features/terminals/TerminalView.test.tsx index 5ba23e4..6e60038 100644 --- a/frontend/src/features/terminals/TerminalView.test.tsx +++ b/frontend/src/features/terminals/TerminalView.test.tsx @@ -455,6 +455,52 @@ describe("TerminalView — visible launch-failure surface (ticket #14 F3)", () = fitSpy.mockRestore(); }); + it("keeps fitting across a keyed project remount until the new cell settles", async () => { + // ProjectsView keys LayoutGrid by project. A project switch therefore + // unmounts/remounts TerminalView, so the mount path itself must keep a + // short tail of fits alive while the new cell geometry settles. + const fitSpy = vi.spyOn(FitAddon.prototype, "fit"); + const firstHandle = makeHandle({ sessionId: "project-1" }); + const secondHandle = makeHandle({ sessionId: "project-2" }); + const open = vi + .fn() + .mockResolvedValueOnce(firstHandle) + .mockResolvedValueOnce(secondHandle); + + function ProjectTerminal({ projectId }: { projectId: string }) { + return ( + + + + ); + } + + const { rerender } = render(); + await waitFor(() => expect(open).toHaveBeenCalledTimes(1)); + setTerminalBoxSize(400, 200); + await waitFor(() => expect(firstHandle.resize).toHaveBeenCalled()); + + fitSpy.mockClear(); + rerender(); + await waitFor(() => expect(open).toHaveBeenCalledTimes(2)); + setTerminalBoxSize(520, 260); + + for (let i = 0; i < 8; i += 1) { + await new Promise((resolve) => requestAnimationFrame(resolve)); + } + + expect(fitSpy.mock.calls.length).toBeGreaterThan(5); + await waitFor(() => expect(secondHandle.resize).toHaveBeenCalled()); + expect(firstHandle.close).not.toHaveBeenCalled(); + expect(secondHandle.close).not.toHaveBeenCalled(); + + fitSpy.mockRestore(); + }); + it("does not refit when refitSignal is left undefined (no-op for callers that don't pass it)", async () => { const fitSpy = vi.spyOn(FitAddon.prototype, "fit"); const open = vi.fn(async () => makeHandle({ sessionId: "no-signal-1" })); diff --git a/frontend/src/features/terminals/TerminalView.tsx b/frontend/src/features/terminals/TerminalView.tsx index 015067f..6a2e8b9 100644 --- a/frontend/src/features/terminals/TerminalView.tsx +++ b/frontend/src/features/terminals/TerminalView.tsx @@ -368,7 +368,11 @@ export function TerminalView({ // manual resize. This stays bounded and preserves the rows/cols-changed // guard before touching the PTY. const SETTLE_REFIT_FRAMES = 4; - const MAX_ZERO_SIZE_RETRIES = 8; + // Project switches remount LayoutGrid/TerminalView by key. The new cell can + // report a non-zero but still intermediate box for more than the ordinary + // transition tail, so the mount path keeps fitting a little longer. + const MOUNT_SETTLE_REFIT_FRAMES = 12; + const MAX_ZERO_SIZE_RETRIES = MOUNT_SETTLE_REFIT_FRAMES; let zeroSizeRetries = 0; const refit = () => { rafId = 0; @@ -418,7 +422,7 @@ export function TerminalView({ }; const ro = new ResizeObserver(() => scheduleRefit()); ro.observe(container); - scheduleSettledRefit(); + scheduleRefit(MOUNT_SETTLE_REFIT_FRAMES); window.addEventListener("resize", scheduleSettledRefit); window.addEventListener("focus", scheduleSettledRefit); window.addEventListener("pageshow", scheduleSettledRefit);