feat(inter-agent): surface frontend des annonces live sur les cellules (F1-F3)
Affiche les annonces d'agent en direct au-dessus des cellules, sur la base develop : - announcements: nouveau feature module — store réactif, provider d'abonnement aux événements, overlay par cible et aperçu, avec tests (announcementsStore + provider). - App.tsx: montage du provider dans l'arbre applicatif. - domain/index.ts: types partagés de l'événement d'annonce côté front. - layout: composition de l'overlay dans LayoutGrid + règle d'exclusion couverte par overlayExclusion.test.ts. - AgentsPanel: intègre l'aperçu des annonces dans la surface existante. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -34,9 +34,37 @@ import {
|
||||
} from "@/features/terminals";
|
||||
import { useGateways } from "@/app/di";
|
||||
import { useProjectWorkState } from "@/features/workstate/useProjectWorkState";
|
||||
import {
|
||||
TargetAnnouncementsOverlay,
|
||||
useTargetAnnouncements,
|
||||
} from "@/features/announcements";
|
||||
import { leaves, normalizeWeights, resizeAdjacent } from "./layout";
|
||||
import { useLayout, type LayoutViewModel } from "./useLayout";
|
||||
|
||||
/**
|
||||
* Full-cell veil exclusion (ticket #4, Architect arbitrage): the write-portal
|
||||
* veil (delegation injection into the PTY) and the F3 target overlay (another
|
||||
* agent is talking to this one) share the same relative container and both cover
|
||||
* `inset:0`. On the inter-agent injection path the write-portal `overlay` flag and
|
||||
* the target's `busy` state rise *together*, which would stack two veils with
|
||||
* near-duplicate banners.
|
||||
*
|
||||
* Rule: exactly one veil at a time, F3 has priority. So the write-portal veil is
|
||||
* shown only for a pinned agent, when the portal asks for it, **and** the target
|
||||
* is not busy-active (F3 owns the cell then). When busy is not yet set — the brief
|
||||
* window before the mediator marks it — the write-portal veil is the fallback.
|
||||
*
|
||||
* Purely visual: this gates rendering only. The keystroke suspension during
|
||||
* injection (`portal.isSuspended()` in `TerminalView`) is untouched.
|
||||
*/
|
||||
export function shouldShowWritePortalVeil(
|
||||
agentPinned: boolean,
|
||||
writePortalOverlay: boolean,
|
||||
busyActive: boolean,
|
||||
): boolean {
|
||||
return agentPinned && writePortalOverlay && !busyActive;
|
||||
}
|
||||
|
||||
interface LayoutGridProps {
|
||||
/** Project whose layout to render. */
|
||||
projectId: string;
|
||||
@ -313,6 +341,10 @@ function LeafView({
|
||||
|
||||
// Build the terminal opener based on whether an agent is pinned.
|
||||
const agentId = agent ?? null;
|
||||
// F3 lifecycle state (busy) for this cell's agent — shared here so the
|
||||
// write-portal veil can be mutually excluded with the F3 overlay (exactly one
|
||||
// full-cell veil at a time, F3 first). Same source the overlay itself reads.
|
||||
const { active: busyActive } = useTargetAnnouncements(agentId ?? "");
|
||||
const agentWork = agentId
|
||||
? workState?.agents.find((row) => row.agentId === agentId)
|
||||
: undefined;
|
||||
@ -836,8 +868,9 @@ function LeafView({
|
||||
/>
|
||||
{/* Write-portal overlay (ARCHITECTURE §20.3 step b/e): while a delegation
|
||||
is being injected into the agent's PTY, a grey veil with a centred
|
||||
message sits above the terminal. Only ever shown for an agent cell. */}
|
||||
{agentId != null && overlay && (
|
||||
message sits above the terminal. Only ever shown for an agent cell —
|
||||
and never together with the F3 overlay (exactly one veil, F3 first). */}
|
||||
{shouldShowWritePortalVeil(agentId != null, Boolean(overlay), busyActive) && (
|
||||
<div
|
||||
data-testid="write-portal-overlay"
|
||||
role="status"
|
||||
@ -868,6 +901,14 @@ function LeafView({
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{/* F3 (ticket #4): while another agent is talking to THIS agent (target),
|
||||
an availability veil showing its live réflexions. Driven by the target's
|
||||
busy state (agentBusyChanged + read-model hydration), never by the raw
|
||||
PTY — it retracts at idle even when a turn ends without a completion
|
||||
event. Self-guards on `active` (busy) and renders null otherwise. */}
|
||||
{agentId != null && (
|
||||
<TargetAnnouncementsOverlay projectId={projectId} agentId={agentId} />
|
||||
)}
|
||||
</div>
|
||||
{busyNotice && (
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user