From 8deaa1fd6c8bdda1c6a1ec3901a05e39aeb98bf5 Mon Sep 17 00:00:00 2001 From: Blomios Date: Sat, 22 Aug 2026 10:02:41 +0200 Subject: [PATCH 1/2] =?UTF-8?q?fix(cli):=20store=20annonces=20inter-agent?= =?UTF-8?q?=20born=C3=A9=20par=20projectId=20avec=20reset=20et=20r=C3=A9hy?= =?UTF-8?q?dratation=20busy=20au=20changement=20de=20projet=20=E2=80=94=20?= =?UTF-8?q?#236?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .ideai/tickets/236/carnet.md | 25 ++++++ .ideai/tickets/236/issue.md | 17 ++++ frontend/src/app/App.tsx | 3 - .../AnnouncementsProvider.test.tsx | 89 ++++++++++++++++++- .../announcements/AnnouncementsProvider.tsx | 25 ++++-- .../LayoutGrid.cellControlsLayering.test.tsx | 2 +- .../src/features/projects/ProjectsView.tsx | 32 ++++--- 7 files changed, 167 insertions(+), 26 deletions(-) create mode 100644 .ideai/tickets/236/carnet.md create mode 100644 .ideai/tickets/236/issue.md diff --git a/.ideai/tickets/236/carnet.md b/.ideai/tickets/236/carnet.md new file mode 100644 index 00000000..135099b4 --- /dev/null +++ b/.ideai/tickets/236/carnet.md @@ -0,0 +1,25 @@ +--- +issueRef: "#236" +version: 12 +updatedBy: {"kind":"agent","agent_id":"a6c6ea12-bfc6-4bdc-8031-324102dfa34d"} +updatedAt: 1787385711767 +--- +2026-08-21 Main +- Bug signalé: dans la custom CLI, la conversation s'affiche bien au début puis les `progress`, messages MCP et assimilés cessent de s'afficher au bout d'un moment. +- Architect: suspect prioritaire frontend, dans `frontend/src/features/agents/CustomAgentChatView.tsx`, autour de `receive`, `foldChunk`, `appendProgress`, `progressUpsertKey`, `progressEntityKey`. +- Hypothèse principale confirmée par DevFrontend: collisions de clé / upsert trop agressif sur `progress` `tool`/`mcp` sans `progressId`, causant des remplacements au lieu d'un enrichissement correct de l'historique visible. +- DevFrontend a corrigé la logique legacy: cycles successifs sans `progressId` restent visibles; `completed` finalise puis libère la clé active; doublons provider/IdeA d'une même complétion restent dédupliqués. +- Fichiers modifiés: `frontend/src/features/agents/CustomAgentChatView.tsx`, `frontend/src/features/agents/CustomAgentChatView.test.tsx`. +- QA: VERT. Commandes exécutées: `npm test -- src/features/agents/CustomAgentChatView.test.tsx` => 55/55 verts; `npm test -- src/features/agents` => 151/151 verts; `npm run typecheck` => OK. +- Git a commité le correctif: `aaf628f` `fix(cli): préserve l'affichage des progress legacy sans progressId via clés d'occurrence dédiées — #236`. +- Git a mergé localement le fix dans `develop` via `--no-ff`: `2c8c71c` `merge(cli): les messages progress/MCP ne disparaissent plus après un certain temps — #236 (QA verte)`. +- Aucun push effectué. Ticket clôturé. + +2026-08-22 Main +- Réouverture du ticket pour un cas résiduel distinct observé utilisateur: un agent est en cours de travail mais le message MCP / l'annonce inter-agent attendu n'apparaît pas sur la surface des messages. +- UX: comportement attendu = preview côté demandeur + overlay côté cible visibles pendant le travail, puis purge à idle; après changement de projet, aucune fuite visuelle inter-projet, et réhydratation correcte dans le projet courant. +- Architect: diagnostic le plus probable = store/provider d'annonces non scopé par `projectId`; l'état app-global survit au switch, `seedBusy` n'hydrate que si absent, et un tour déjà en cours peut donc ne jamais remonter visuellement dans le nouveau projet. +- Correctif minimal demandé au frontend: `reset + filter + rehydrate` à chaque changement de `projectId`, idéalement avec un store project-scoped. +- Git: branche locale retenue `fix/236-annonces-store-par-projectid` depuis `develop` @ `85f659c`. +- DevFrontend: diagnostic confirmé = provider app-global non borné par projet; correctif implémenté = provider explicitement borné par `projectId`, reset de `index + busy` au changement de projet, filtrage des `agentAnnouncement` par `event.projectId`, tests ciblés verts et `npm run typecheck` vert. +- Risque résiduel signalé par DevFrontend: `agentBusyChanged` ne porte pas encore `projectId`, donc le lifecycle live busy ne peut pas être filtré aussi strictement que les annonces; le bug visé est néanmoins corrigé par reset + réhydratation au switch. \ No newline at end of file diff --git a/.ideai/tickets/236/issue.md b/.ideai/tickets/236/issue.md new file mode 100644 index 00000000..318c2da2 --- /dev/null +++ b/.ideai/tickets/236/issue.md @@ -0,0 +1,17 @@ +--- +id: "5c03537c-2daf-4802-a6fb-6c8aa92b8aa7" +number: 236 +title: "Custom CLI: les messages progress/MCP disparaissent après un certain temps" +status: "inProgress" +priority: "critical" +sprint: null +links: [] +agentRefs: [] +attachments: [] +createdBy: {"kind":"agent","agent_id":"a6c6ea12-bfc6-4bdc-8031-324102dfa34d"} +updatedBy: {"kind":"agent","agent_id":"a6c6ea12-bfc6-4bdc-8031-324102dfa34d"} +createdAt: 1787326716055 +updatedAt: 1787385711767 +version: 12 +--- +Bug sur l'affichage des conversations dans la custom CLI: l'historique s'affiche correctement au début, puis au bout d'un moment les messages `progress`, usages MCP et assimilés cessent de s'afficher. Il faut reproduire, diagnostiquer la cause et corriger sans casser l'affichage initial. \ No newline at end of file diff --git a/frontend/src/app/App.tsx b/frontend/src/app/App.tsx index a470d066..19fd7383 100644 --- a/frontend/src/app/App.tsx +++ b/frontend/src/app/App.tsx @@ -9,7 +9,6 @@ import { useEffect, useState } from "react"; import type { DomainEvent, HealthReport } from "@/domain"; import { ProjectsView } from "@/features/projects"; import { FirstRunWizard } from "@/features/first-run"; -import { AnnouncementsProvider } from "@/features/announcements"; import { AppExitConfirmDialog } from "@/features/appExit"; import { PluginRuntimeProvider } from "@/features/plugins"; import { Panel, Spinner, Toolbar } from "@/shared"; @@ -65,7 +64,6 @@ export function App() { }, [profile]); return ( -
{/* ── Header ── */} @@ -119,7 +117,6 @@ export function App() {
-
); } diff --git a/frontend/src/features/announcements/AnnouncementsProvider.test.tsx b/frontend/src/features/announcements/AnnouncementsProvider.test.tsx index 82daa87f..27ac6c88 100644 --- a/frontend/src/features/announcements/AnnouncementsProvider.test.tsx +++ b/frontend/src/features/announcements/AnnouncementsProvider.test.tsx @@ -24,17 +24,19 @@ import { TargetAnnouncementsOverlay } from "./TargetAnnouncementsOverlay"; function setup( node: React.ReactNode, - opts: { workState?: MockWorkStateGateway } = {}, + opts: { projectId?: string | null; workState?: MockWorkStateGateway } = {}, ) { const system = new MockSystemGateway(); const workState = opts.workState; const gateways = { system, workState } as unknown as Gateways; - render( + const result = render( - {node} + + {node} + , ); - return { system }; + return { system, ...result }; } type AnnouncementEvent = Extract; @@ -84,6 +86,27 @@ describe("F3 — overlay lifecycle is the target's busy state", () => { expect(screen.getByText("en cours")).not.toBeNull(); }); + it("ignores streamed announcements from another project", async () => { + const { system } = setup( + , + ); + await flush(); + + act(() => { + system.emit(busy("agent-T", true)); + system.emit( + announcement({ + projectId: "p2", + target: "agent-T", + text: "other-project", + }), + ); + }); + + expect(overlay()).not.toBeNull(); + expect(screen.queryByText("other-project")).toBeNull(); + }); + it("mounts on busy:true even before any announcement (banner only)", async () => { const { system } = setup( , @@ -171,6 +194,64 @@ describe("F3 — overlay lifecycle is the target's busy state", () => { expect(overlay()).not.toBeNull(); }); + it("resets and rehydrates when switching project", async () => { + const workState = new MockWorkStateGateway(); + workState._setProjectWorkState("p1", { + agents: [ + { + agentId: "agent-T", + name: "Target", + profileId: "prof", + busy: { state: "idle" }, + tickets: [], + }, + ], + conversations: [], + }); + workState._setProjectWorkState("p2", { + agents: [ + { + agentId: "agent-T", + name: "Target", + profileId: "prof", + busy: { state: "busy", ticket: "t-p2", sinceMs: 456 }, + tickets: [], + }, + ], + conversations: [], + }); + + const system = new MockSystemGateway(); + const gateways = { system, workState } as unknown as Gateways; + const { rerender } = render( + + + + + , + ); + await flush(); + + act(() => { + system.emit(busy("agent-T", true)); + system.emit(announcement({ projectId: "p1", text: "old-p1" })); + }); + expect(screen.getByText("old-p1")).not.toBeNull(); + + rerender( + + + + + , + ); + await flush(); + + expect(overlay()).not.toBeNull(); + expect(screen.queryByText("old-p1")).toBeNull(); + expect(screen.getByText("Travaille sur une demande en cours.")).not.toBeNull(); + }); + it("stays down when the read-model reports the agent idle", async () => { const workState = new MockWorkStateGateway(); workState._setProjectWorkState("p1", { diff --git a/frontend/src/features/announcements/AnnouncementsProvider.tsx b/frontend/src/features/announcements/AnnouncementsProvider.tsx index acc33717..26265c44 100644 --- a/frontend/src/features/announcements/AnnouncementsProvider.tsx +++ b/frontend/src/features/announcements/AnnouncementsProvider.tsx @@ -1,5 +1,5 @@ /** - * `AnnouncementsProvider` — the live, app-wide store for inter-agent announcements + * `AnnouncementsProvider` — the live, project-scoped store for inter-agent announcements * (ticket #4, F1 wiring). Subscribes **once** to the domain-event stream via the * {@link SystemGateway} and folds two orthogonal signals: * @@ -22,8 +22,9 @@ * busy is the sole lifecycle authority, which is exactly what we want.) * * Consumers read via {@link useTargetAnnouncements} (F3) and - * {@link useRequesterAnnouncements} (F2). The store is never persisted — it is - * rebuilt live from the event stream and the read-model snapshot. + * {@link useRequesterAnnouncements} (F2). The store is never persisted and is + * reset whenever the provider's project changes; it is rebuilt live from the + * event stream and the read-model snapshot for that project. */ import { @@ -76,7 +77,13 @@ const DEFAULT_STORE: AnnouncementsStore = { const AnnouncementsContext = createContext(null); -export function AnnouncementsProvider({ children }: { children: ReactNode }) { +export function AnnouncementsProvider({ + projectId, + children, +}: { + projectId: string | null; + children: ReactNode; +}) { const { system } = useGateways(); const [index, setIndex] = useState(emptyAnnouncementIndex); const [busy, setBusy] = useState({}); @@ -87,15 +94,21 @@ export function AnnouncementsProvider({ children }: { children: ReactNode }) { setBusy((prev) => (agentId in prev ? prev : { ...prev, [agentId]: value })); }, []); + useEffect(() => { + setIndex(emptyAnnouncementIndex()); + setBusy({}); + }, [projectId]); + useEffect(() => { // `system` may be absent in unit tests injecting a partial gateway set. - if (!system) return; + if (!system || !projectId) return; let unsubscribe: (() => void) | undefined; let cancelled = false; void system .onDomainEvent((event) => { if (event.type === "agentAnnouncement") { + if (event.projectId !== projectId) return; // Content of the overlay/preview. setIndex((prev) => appendAnnouncement(prev, { @@ -127,7 +140,7 @@ export function AnnouncementsProvider({ children }: { children: ReactNode }) { cancelled = true; unsubscribe?.(); }; - }, [system]); + }, [system, projectId]); const store = useMemo( () => ({ index, busy, seedBusy }), diff --git a/frontend/src/features/layout/LayoutGrid.cellControlsLayering.test.tsx b/frontend/src/features/layout/LayoutGrid.cellControlsLayering.test.tsx index 901a14ad..e862a294 100644 --- a/frontend/src/features/layout/LayoutGrid.cellControlsLayering.test.tsx +++ b/frontend/src/features/layout/LayoutGrid.cellControlsLayering.test.tsx @@ -123,7 +123,7 @@ function renderGrid( ) { return render( - + , diff --git a/frontend/src/features/projects/ProjectsView.tsx b/frontend/src/features/projects/ProjectsView.tsx index 499ef15a..cd3cf14d 100644 --- a/frontend/src/features/projects/ProjectsView.tsx +++ b/frontend/src/features/projects/ProjectsView.tsx @@ -52,6 +52,7 @@ import { type SettingsSection, } from "@/features/settings"; import { GitGraphView } from "@/features/git"; +import { AnnouncementsProvider } from "@/features/announcements"; import { usePluginMenus, usePluginRuntime } from "@/features/plugins"; import type { WhenContext } from "@/plugins/runtime"; import { @@ -735,7 +736,7 @@ export function ProjectsView() {

Open a project to use this panel.

); } - return ( + const body = ( ); + return panel === "agents" ? ( + {body} + ) : ( + body + ); } // Projects manager renders inline in the welcome area only when it is not @@ -924,17 +930,19 @@ export function ProjectsView() { projectId={active.id} /> ) : ( - { - setSettingsSection("plugins"); - dismissFloating(); - }} - /> + + { + setSettingsSection("plugins"); + dismissFloating(); + }} + /> + )} ) : viewerConversation ? ( From 1e172c6e81c134ce82fc0227f2b4719005d6fab0 Mon Sep 17 00:00:00 2001 From: Blomios Date: Sat, 22 Aug 2026 10:02:41 +0200 Subject: [PATCH 2/2] =?UTF-8?q?chore(tickets):=20cl=C3=B4ture=20#200=20apr?= =?UTF-8?q?=C3=A8s=20QA=20verte=20et=20merge=20local=2085f659c?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .ideai/tickets/200/carnet.md | 8 +++++--- .ideai/tickets/200/issue.md | 6 +++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.ideai/tickets/200/carnet.md b/.ideai/tickets/200/carnet.md index 19cebaf7..84362594 100644 --- a/.ideai/tickets/200/carnet.md +++ b/.ideai/tickets/200/carnet.md @@ -1,8 +1,8 @@ --- issueRef: "#200" -version: 16 +version: 19 updatedBy: {"kind":"agent","agent_id":"a6c6ea12-bfc6-4bdc-8031-324102dfa34d"} -updatedAt: 1787384954575 +updatedAt: 1787385038497 --- 2026-08-14 Main - Reprise du ticket après livraison initiale du lot Codex-centric. @@ -19,4 +19,6 @@ updatedAt: 1787384954575 - Analyse Architect: pas un gap de design; suspicion de chemin secondaire ou de comportement agent qui continue d'utiliser/mentionner `/tmp` explicitement au lieu de suivre l'env temp fourni. - Suite proposée: reproduire le cas réel avec le provider/profil concerné, vérifier la valeur réellement vue dans le process agent, puis décider entre fix runtime (si env perdu/contourné) et renfort d'instruction/runtime API (si l'agent hardcode `/tmp`). - Git a retenu une branche de fix dédiée `fix/200-managed-tmp-injection-regression` depuis `develop`, HEAD `3344361fc27bace7f68953f7a11623af1d8a7d86`. -- DevBackend: diagnostic retenu = l'env temp était déjà injecté, mais le convention file était généré avant l'allocation du temp, donc aucun signal textuel explicite ne guidait l'agent; correctif implémenté = allocation plus tôt dans le launch path, ajout de `IDEA_MANAGED_TMP`, et injection conditionnelle d'une instruction “Temporaire IdeA géré” avec le chemin concret; tests agent_lifecycle renforcés. \ No newline at end of file +- DevBackend: diagnostic retenu = l'env temp était déjà injecté, mais le convention file était généré avant l'allocation du temp, donc aucun signal textuel explicite ne guidait l'agent; correctif implémenté = allocation plus tôt dans le launch path, ajout de `IDEA_MANAGED_TMP`, et injection conditionnelle d'une instruction “Temporaire IdeA géré” avec le chemin concret; tests agent_lifecycle renforcés. +- QA 2026-08-22: VERDICT VERT. Commandes validées: `cargo test -p application --test agent_lifecycle launch_pty_allocates_managed_tmp_env_and_sandbox_from_same_path -- --exact --nocapture`, `cargo test -p application --test agent_lifecycle launch_structured_non_codex_uses_managed_tmp_for_env_without_os_sandbox -- --exact --nocapture`, `cargo test -p application --test agent_lifecycle launch_structured_codex_uses_managed_tmp_for_env_and_writable_roots_without_os_sandbox -- --exact --nocapture`, `cargo test -p application --test agent_lifecycle managed_tmp -- --nocapture`. +- Git 2026-08-22: commit local `f8cf86c` `fix(infra): temp IdeA géré alloué avant le convention file, exposé via IDEA_MANAGED_TMP et instruction explicite privilégiant le temp géré à /tmp — #200`; merge local `--no-ff` vers `develop` = `85f659c` `merge(infra): temp IdeA géré visible par l'agent avant le spawn — #200 (QA verte)`; aucun push. \ No newline at end of file diff --git a/.ideai/tickets/200/issue.md b/.ideai/tickets/200/issue.md index 58c99aed..6d952d1c 100644 --- a/.ideai/tickets/200/issue.md +++ b/.ideai/tickets/200/issue.md @@ -2,7 +2,7 @@ id: "0268a6a4-2ceb-4332-a113-29b93a59caf1" number: 200 title: "Remplacer l'usage implicite de /tmp par un temp IdeA géré pour les agents" -status: "inProgress" +status: "closed" priority: "high" sprint: null links: [] @@ -11,7 +11,7 @@ attachments: [] createdBy: {"kind":"agent","agent_id":"a6c6ea12-bfc6-4bdc-8031-324102dfa34d"} updatedBy: {"kind":"agent","agent_id":"a6c6ea12-bfc6-4bdc-8031-324102dfa34d"} createdAt: 1786743649446 -updatedAt: 1787384954575 -version: 16 +updatedAt: 1787385038497 +version: 19 --- Extension du lot temp IdeA géré. Le temp principal launch-scoped existe déjà mais le comportement doit maintenant être généralisé à tous les agents, pas seulement Codex, afin que n'importe quel agent puisse utiliser le répertoire temporaire IdeA via ses variables d'environnement/runtime appropriées. En plus du release au stop de session déjà en place, IdeA doit purger le contenu de ce stockage temporaire à la fermeture de l'application pour éviter l'accumulation résiduelle. Le lot couvre donc: projection cross-agent du temp IdeA principal, cohérence sandbox/runtime selon les profils, et purge à l'extinction d'IdeA. \ No newline at end of file