chantier: durcissement reprise headless inter-projets + persistance profils Opencode

- Implémentation reprise de session Opencode headless (conversational_recovery)
- Persistance des profils IA Opencode dans .ideai/memory avec JSON Schema
- Gestion des permissions MCP pour agents externes
- Tests QA verts : structured_launch_d3, conversation_log, tickets_missing_carnet, agents, ProfilesSettings
This commit is contained in:
2026-07-28 12:23:34 +02:00
parent 78f7c8fe2d
commit 045e0984cc
15 changed files with 782 additions and 49 deletions

View File

@ -1,4 +1,4 @@
import { describe, expect, it } from "vitest";
import { describe, expect, it, vi } from "vitest";
import { fireEvent, render, screen, waitFor, within } from "@testing-library/react";
import { DIProvider } from "@/app/di";
@ -221,4 +221,70 @@ describe("ProfilesSettings", () => {
),
).toBeTruthy();
});
it("creates OpenCode profiles through the OpenCode clone flow and persists them for agent pickers", async () => {
const profile = new MockProfileGateway();
const cloneOpenCodeSpy = vi.spyOn(profile, "cloneOpenCodeProfileFromSeed");
const cloneGenericSpy = vi.spyOn(profile, "cloneProfileFromSeed");
renderSettings(profile);
await waitReady();
fireEvent.click(screen.getByRole("tab", { name: "OpenCode-local" }));
await waitReady();
fireEvent.click(screen.getByRole("button", { name: "Creer un profil" }));
const name = await screen.findByDisplayValue("OpenCode + llama.cpp copy");
expect(name).toBeTruthy();
await waitFor(async () => {
const saved = await profile.listProfiles();
expect(saved.map((p) => p.name)).toContain("OpenCode + llama.cpp copy");
expect(saved.some((p) => p.structuredAdapter === "openCode")).toBe(true);
});
expect(cloneOpenCodeSpy).toHaveBeenCalledWith({
name: "OpenCode + llama.cpp copy",
opencode: expect.objectContaining({
baseURL: "http://localhost:8080/v1",
model: "qwen3-coder-30b",
}),
});
expect(cloneGenericSpy).not.toHaveBeenCalled();
});
it("duplicates OpenCode profiles through the OpenCode clone flow and preserves their local config", async () => {
const profile = new MockProfileGateway();
renderSettings(profile);
await waitReady();
fireEvent.click(screen.getByRole("tab", { name: "OpenCode-local" }));
await waitReady();
await createProfile();
const row = screen.getAllByRole("listitem")[0];
fireEvent.change(within(row).getByLabelText("OpenCode + llama.cpp copy base url"), {
target: { value: "http://localhost:9090/v1" },
});
fireEvent.change(within(row).getByLabelText("OpenCode + llama.cpp copy model"), {
target: { value: "local-special" },
});
fireEvent.click(within(row).getByRole("button", { name: "Enregistrer" }));
await waitFor(async () => {
const saved = await profile.listProfiles();
expect(saved[0]?.opencode?.baseURL).toBe("http://localhost:9090/v1");
});
fireEvent.click(within(screen.getAllByRole("listitem")[0]).getByRole("button", {
name: "Dupliquer",
}));
await waitFor(async () => {
const saved = await profile.listProfiles();
const copies = saved.filter((p) => p.structuredAdapter === "openCode");
expect(copies).toHaveLength(2);
expect(copies[1]?.name).toBe("OpenCode + llama.cpp copy copy");
expect(copies[1]?.opencode?.baseURL).toBe("http://localhost:9090/v1");
expect(copies[1]?.opencode?.model).toBe("local-special");
});
});
});