diff --git a/CHANGELOG.md b/CHANGELOG.md index 63f6c6e6b..5a64ac249 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,17 @@ Newest first. `Unreleased` is what is on `main` and not yet tagged. ## Unreleased +### Hiding a coworker no longer hides the grants pointing at it + +Hiding a coworker is a preference about your own roster — one row per person — and the grants saying +which Bots may hand work to it are a deployment-wide fact an administrator set. The Handoff section +joined the two, so hiding a coworker from your roster took every grant aimed at it off the screen: +the switch was gone, no note said why, and the count above the list quietly dropped by one. Those +grants were still in force, because a hop is decided by the grant and not by anybody's roster, so +the coworker went on being asked while the only screen that could stop it had stopped listing it. +A coworker you have hidden now appears in that list when a grant already points at it, marked as +hidden from your roster, so it can be switched off. One you have hidden with nothing granted to it +stays hidden. ### A tool call that failed no longer reads as one that worked The audit page draws a row it does not recognise as `Allowed`, which is right for the many rows that diff --git a/app/src/components/agents/handoff-panel.tsx b/app/src/components/agents/handoff-panel.tsx index 2c7a7a58d..393971727 100644 --- a/app/src/components/agents/handoff-panel.tsx +++ b/app/src/components/agents/handoff-panel.tsx @@ -15,6 +15,7 @@ import { ItemTitle, } from "@/components/ui/item"; import { Switch } from "@/components/ui/switch"; +import { handoffRoster } from "@/lib/agents/handoff-roster"; import { setHandoffGrantMutationOptions } from "@/lib/agents/mutations"; import { agentHandoffQueryOptions, @@ -38,29 +39,27 @@ export function HandoffPanel({ agentId }: { agentId: string }) { const queryClient = useQueryClient(); const handoff = useQuery(agentHandoffQueryOptions(agentId)); const agents = useQuery(agentListQueryOptions()); + /* + * The roster this person has hidden, read so a grant pointing into it can still be taken away. + * + * Hiding is a per-person display preference and the grants are not filtered by it at all, so + * joining the grants against the visible roster alone dropped live grants off the only screen that + * manages them. See `handoffRoster`, which is where that join now happens. + */ + const hiddenAgents = useQuery(agentListQueryOptions(true)); const setGrant = useMutation(setHandoffGrantMutationOptions(queryClient)); if (handoff.isPending || !handoff.data) return null; const { enabled, canGrant, reachable, grantable } = handoff.data; - /* - * A Bot may not be granted itself, and the server refuses it, so it is not offered here either. - * Hidden Bots are already absent from this list. - */ - const others = (agents.data ?? []).filter( - (candidate) => candidate.id !== agentId, - ); - const granted = others.filter((candidate) => - reachable.includes(candidate.id), - ).length; - /* - * On a Bot that cannot be a grantee only the leftovers are shown: a stale grant may still be - * revoked — taking away is always allowed — but offering switches that can only bounce off the - * server's refusal is the thing the explanation item above replaces. - */ - const candidates = grantable - ? others - : others.filter((candidate) => reachable.includes(candidate.id)); + // A Bot may not be granted itself, and the server refuses it, so it is not offered here either. + const { candidates, granted, total } = handoffRoster({ + agentId, + roster: agents.data ?? [], + hidden: hiddenAgents.data ?? [], + reachable, + grantable, + }); // Nothing to say to somebody who cannot change it and has nothing to read. if (!canGrant && reachable.length === 0) return null; @@ -72,9 +71,9 @@ export function HandoffPanel({ agentId }: { agentId: string }) { Bots it may ask {/* The current answer at a glance, so the list below is detail rather than homework. */} - {grantable && others.length > 0 ? ( + {grantable && total > 0 ? ( - {granted} of {others.length} + {granted} of {total} ) : null} @@ -121,7 +120,7 @@ export function HandoffPanel({ agentId }: { agentId: string }) {

) : null} - {grantable && others.length === 0 ? ( + {grantable && total === 0 ? ( @@ -147,7 +146,16 @@ export function HandoffPanel({ agentId }: { agentId: string }) { {candidate.name} - {candidate.title} + {/* + * Said on the row, because otherwise it is a coworker that is not on your roster + * appearing in a list with no explanation. It is here only because this Bot may + * already ask it, and that is the sentence a person needs to decide what to do. + */} + + {candidate.hidden + ? `${candidate.title} · hidden from your roster` + : candidate.title} +