Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 42 additions & 30 deletions apps/web/src/bench/grants-tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,14 @@ function resourceName(resource: string): string {
if (!resource.startsWith(TOOL_PREFIX)) {
return GRANT_RESOURCE_LABEL[resource as GrantResource] ?? resource;
}
const words = resource
const text = resource
.slice(TOOL_PREFIX.length)
.replace(/[_.-]+/g, " ")
.trim();
return words.charAt(0).toUpperCase() + words.slice(1);
.split(/[_.\-\s]+/)
.filter(
(word, index, all) => word !== "" && word.toLowerCase() !== all[index - 1]?.toLowerCase(),
)
.join(" ");
return text.charAt(0).toUpperCase() + text.slice(1);
}

/** This workbench's own grants; they never inherit from the workspace. */
Expand All @@ -40,14 +43,15 @@ export function GrantsTab({
readonly participants: readonly WorkbenchParticipant[];
}) {
const names = useGrantNames(workbenchTenantId);
const who = (grant: Grant): string => {
const who = (grant: Grant): string | undefined => {
if (grant.roleName !== undefined && grant.roleName !== null) return grant.roleName;
const known = participants.find((p) => p.id === grant.principalId);
if (known !== undefined) return known.name;
if (grant.principalName === undefined || grant.principalName === null) return "Everyone here";
const named = names.replaceTenantIds(grant.principalName);
// A raw run id is never shown, and never turned into words.
return principalLabel(named).raw === null ? named : "A worker";
// A raw run id is never shown, and never turned into words; with no
// resolvable name the row has no subtitle.
return principalLabel(named).raw === null ? named : undefined;
};
const queryClient = useQueryClient();
const query = toAPIQuery<readonly Grant[]>(
Expand Down Expand Up @@ -81,30 +85,38 @@ export function GrantsTab({
<p className="workbench-info-empty-note">No grants here yet.</p>
) : (
<ul className="bench-tab-list">
{grants.map((grant) => (
<li key={grant.id} className="bench-tab-row">
<span className="bench-tab-text">
<span className="workbench-info-cell-primary">
{names.resource(grant.resource) ?? resourceName(grant.resource)}
{grants.map((grant) => {
const title = names.resource(grant.resource) ?? resourceName(grant.resource);
const name = who(grant);
const subtitle =
name !== undefined && title.toLowerCase().split(" ").includes(name.toLowerCase())
? undefined
: name;
return (
<li key={grant.id} className="bench-tab-row">
<span className="bench-tab-text">
<span className="workbench-info-cell-primary">{title}</span>
{subtitle === undefined ? null : (
<span className="workbench-info-cell-context">{subtitle}</span>
)}
</span>
<span className="workbench-info-cell-context">{who(grant)}</span>
</span>
<span className="drawer-grant-mode" data-effect={grant.effect}>
{MODE[grant.effect]}
</span>
<ConfirmButton
variant="ghost"
size="sm"
disabled={revoke.isPending}
confirmLabel="Revoke?"
onConfirm={() => {
revoke.mutate(grant);
}}
>
Revoke
</ConfirmButton>
</li>
))}
<span className="drawer-grant-mode" data-effect={grant.effect}>
{MODE[grant.effect]}
</span>
<ConfirmButton
variant="ghost"
size="sm"
disabled={revoke.isPending}
confirmLabel="Revoke?"
onConfirm={() => {
revoke.mutate(grant);
}}
>
Revoke
</ConfirmButton>
</li>
);
})}
</ul>
)
}
Expand Down
8 changes: 5 additions & 3 deletions apps/web/src/chat/avatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,10 @@ export function WorkbenchAvatarStack({
return <AvatarStack items={items} {...(max === undefined ? {} : { max })} />;
}

// Color hashes off principal id, never name — DESIGN.md's avatar identity
// rule.
// A person's color hashes off principal id, never name — DESIGN.md's avatar
// identity rule. A worker keeps the agent fill: each bench deploys its own
// copy with a new principal id, so hashing it would recolor one worker per
// bench.
export function IdentityAvatar({
kind,
name,
Expand All @@ -171,7 +173,7 @@ export function IdentityAvatar({
<WorkbenchAvatar
kind={kind === "agent" ? "worker" : "person"}
name={name}
tone={TONE_BY_COLOR[avatarColorForPrincipal(principalId)]}
tone={kind === "agent" ? "agent" : TONE_BY_COLOR[avatarColorForPrincipal(principalId)]}
size="sm"
{...(status === undefined ? {} : { status })}
/>
Expand Down
8 changes: 6 additions & 2 deletions apps/web/src/pages/workers-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,12 @@ export function WorkersRosterList({
</Button>
) : null}
</span>
<span className="truncate text-[12.5px] font-semibold text-(--ink-2)">
{(byWorker.get(agent.id) ?? []).map((bench) => bench.name).join(", ")}
<span className="flex min-w-0 flex-wrap gap-x-2 text-[12.5px] font-semibold text-(--ink-2)">
{(byWorker.get(agent.id) ?? []).map((bench) => (
<span key={bench.id} className="max-w-full truncate">
{bench.name}
</span>
))}
</span>
<span className="text-right text-[12.5px] text-(--ink-3)">
{agent.liveAddress === null ? "" : "Now"}
Expand Down
Loading