merge feature/frontend-mcp-panel-terminal-refit-fixes dans develop (panneau permissions idea_ask_agents + fit terminal au remount projet)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -2462,6 +2462,7 @@ export class MockPermissionGateway implements PermissionGateway {
|
|||||||
],
|
],
|
||||||
writeActionTools: [
|
writeActionTools: [
|
||||||
"idea_ask_agent",
|
"idea_ask_agent",
|
||||||
|
"idea_ask_agents",
|
||||||
"idea_run_in_background",
|
"idea_run_in_background",
|
||||||
"idea_launch_agent",
|
"idea_launch_agent",
|
||||||
"idea_stop_agent",
|
"idea_stop_agent",
|
||||||
|
|||||||
@ -108,6 +108,19 @@ describe("PermissionsPanel — Tools MCP IdeA tab", () => {
|
|||||||
expect(screen.getByRole("button", { name: "Enregistrer" })).toHaveProperty("disabled", true);
|
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 () => {
|
it("creating an override prefills the draft with the current effective allowlist", async () => {
|
||||||
await renderPanel();
|
await renderPanel();
|
||||||
await openMcpToolsTab();
|
await openMcpToolsTab();
|
||||||
|
|||||||
@ -39,7 +39,12 @@ export const MCP_TOOL_GROUPS: McpToolGroupDef[] = [
|
|||||||
{
|
{
|
||||||
id: "delegation",
|
id: "delegation",
|
||||||
label: "Délégation agents",
|
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",
|
id: "contextMemory",
|
||||||
@ -83,6 +88,7 @@ export const MCP_TOOL_LABELS: Record<string, string> = {
|
|||||||
idea_ticket_read_carnet: "Lire le carnet d'un ticket",
|
idea_ticket_read_carnet: "Lire le carnet d'un ticket",
|
||||||
idea_sprint_list: "Lister les sprints",
|
idea_sprint_list: "Lister les sprints",
|
||||||
idea_ask_agent: "Déléguer à un agent",
|
idea_ask_agent: "Déléguer à un agent",
|
||||||
|
idea_ask_agents: "Solliciter plusieurs agents",
|
||||||
idea_launch_agent: "Lancer un agent",
|
idea_launch_agent: "Lancer un agent",
|
||||||
idea_stop_agent: "Arrêter un agent",
|
idea_stop_agent: "Arrêter un agent",
|
||||||
idea_update_context: "Modifier le contexte d'un agent",
|
idea_update_context: "Modifier le contexte d'un agent",
|
||||||
|
|||||||
@ -455,6 +455,52 @@ describe("TerminalView — visible launch-failure surface (ticket #14 F3)", () =
|
|||||||
fitSpy.mockRestore();
|
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 (
|
||||||
|
<DIProvider gateways={{ terminal: new MockTerminalGateway() } as unknown as Gateways}>
|
||||||
|
<TerminalView
|
||||||
|
key={projectId}
|
||||||
|
cwd={`/cwd/${projectId}`}
|
||||||
|
open={open}
|
||||||
|
/>
|
||||||
|
</DIProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { rerender } = render(<ProjectTerminal projectId="p1" />);
|
||||||
|
await waitFor(() => expect(open).toHaveBeenCalledTimes(1));
|
||||||
|
setTerminalBoxSize(400, 200);
|
||||||
|
await waitFor(() => expect(firstHandle.resize).toHaveBeenCalled());
|
||||||
|
|
||||||
|
fitSpy.mockClear();
|
||||||
|
rerender(<ProjectTerminal projectId="p2" />);
|
||||||
|
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 () => {
|
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 fitSpy = vi.spyOn(FitAddon.prototype, "fit");
|
||||||
const open = vi.fn(async () => makeHandle({ sessionId: "no-signal-1" }));
|
const open = vi.fn(async () => makeHandle({ sessionId: "no-signal-1" }));
|
||||||
|
|||||||
@ -368,7 +368,11 @@ export function TerminalView({
|
|||||||
// manual resize. This stays bounded and preserves the rows/cols-changed
|
// manual resize. This stays bounded and preserves the rows/cols-changed
|
||||||
// guard before touching the PTY.
|
// guard before touching the PTY.
|
||||||
const SETTLE_REFIT_FRAMES = 4;
|
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;
|
let zeroSizeRetries = 0;
|
||||||
const refit = () => {
|
const refit = () => {
|
||||||
rafId = 0;
|
rafId = 0;
|
||||||
@ -418,7 +422,7 @@ export function TerminalView({
|
|||||||
};
|
};
|
||||||
const ro = new ResizeObserver(() => scheduleRefit());
|
const ro = new ResizeObserver(() => scheduleRefit());
|
||||||
ro.observe(container);
|
ro.observe(container);
|
||||||
scheduleSettledRefit();
|
scheduleRefit(MOUNT_SETTLE_REFIT_FRAMES);
|
||||||
window.addEventListener("resize", scheduleSettledRefit);
|
window.addEventListener("resize", scheduleSettledRefit);
|
||||||
window.addEventListener("focus", scheduleSettledRefit);
|
window.addEventListener("focus", scheduleSettledRefit);
|
||||||
window.addEventListener("pageshow", scheduleSettledRefit);
|
window.addEventListener("pageshow", scheduleSettledRefit);
|
||||||
|
|||||||
Reference in New Issue
Block a user