feat: implement unconditionally dropping of CLI provisioner artifacts

- Added a new function to drop CLI provisioner members from the member map, ensuring these internal artifacts are never displayed to users.
- Updated the createCliProvisionerNameGuard function to unconditionally hide provisioner names, regardless of the presence of base members.
- Modified unit tests to reflect the new behavior of dropping provisioner names even when the base member is absent.
This commit is contained in:
iliya 2026-03-15 11:18:49 +02:00
parent 65a9928cb5
commit 96c9b00d92
4 changed files with 33 additions and 16 deletions

View file

@ -370,6 +370,9 @@ export class TeamMemberLogsFinder {
results.length = 0; results.length = 0;
results.push(...nonSubagent, ...subagentsByKey.values()); results.push(...nonSubagent, ...subagentsByKey.values());
} }
// NOTE: dedup assumes cumulative snapshots (largest file = superset of all smaller ones).
// Safety net: filterChunksByWorkIntervals on frontend still filters content by time,
// so even if the wrong file is picked, only task-relevant chunks are shown.
const sorted = results.sort( const sorted = results.sort(
(a, b) => new Date(b.startTime).getTime() - new Date(a.startTime).getTime() (a, b) => new Date(b.startTime).getTime() - new Date(a.startTime).getTime()

View file

@ -285,6 +285,26 @@ function dropCliAutoSuffixedMembers(
} }
} }
const PROVISIONER_SUFFIX = '-provisioner';
/**
* Drop CLI provisioner artifacts ("{name}-provisioner") unconditionally.
* These are temporary internal agents created during team provisioning
* and should never be shown to the user.
*/
function dropCliProvisionerMembers(
memberMap: Map<string, { name: string; role?: string; color?: string }>
): void {
for (const [key, member] of Array.from(memberMap.entries())) {
const lower = member.name.trim().toLowerCase();
if (!lower.endsWith(PROVISIONER_SUFFIX)) continue;
const base = lower.slice(0, -PROVISIONER_SUFFIX.length);
if (base) {
memberMap.delete(key);
}
}
}
async function listTeams( async function listTeams(
payload: ListTeamsPayload payload: ListTeamsPayload
): Promise<{ teams: unknown[]; diag: ListTeamsDiag }> { ): Promise<{ teams: unknown[]; diag: ListTeamsDiag }> {
@ -428,6 +448,7 @@ async function listTeams(
} }
dropCliAutoSuffixedMembers(memberMap); dropCliAutoSuffixedMembers(memberMap);
dropCliProvisionerMembers(memberMap);
const members = Array.from(memberMap.values()); const members = Array.from(memberMap.values());
const summary = { const summary = {

View file

@ -43,27 +43,20 @@ const PROVISIONER_SUFFIX = '-provisioner';
/** /**
* Claude CLI creates temporary "{name}-provisioner" agents during team provisioning * Claude CLI creates temporary "{name}-provisioner" agents during team provisioning
* to spawn real teammates. These are internal artifacts and should be hidden when * to spawn real teammates. These are always internal artifacts never real teammates.
* the real base member (e.g. "alice") also exists.
* *
* Only removes "alice-provisioner" if "alice" is present if the base is missing, * Unlike numeric suffixes (alice-2) which can be intentional, "-provisioner" is a
* the provisioner entry is kept for visibility. * hardcoded CLI pattern that should never be exposed to the user. We unconditionally
* hide any name ending with "-provisioner" regardless of whether the base name exists.
*/ */
export function createCliProvisionerNameGuard( export function createCliProvisionerNameGuard(
allNames: Iterable<string> _allNames: Iterable<string>
): (name: string) => boolean { ): (name: string) => boolean {
const allLower = new Set<string>();
for (const n of allNames) {
if (typeof n !== 'string') continue;
const t = n.trim().toLowerCase();
if (t) allLower.add(t);
}
return (name: string): boolean => { return (name: string): boolean => {
const lower = name.trim().toLowerCase(); const lower = name.trim().toLowerCase();
if (!lower.endsWith(PROVISIONER_SUFFIX)) return true; if (!lower.endsWith(PROVISIONER_SUFFIX)) return true;
const base = lower.slice(0, -PROVISIONER_SUFFIX.length); const base = lower.slice(0, -PROVISIONER_SUFFIX.length);
if (!base) return true; // Keep bare "-provisioner" (no base) — that's not a CLI artifact pattern
return !allLower.has(base); return !base;
}; };
} }

View file

@ -56,10 +56,10 @@ describe('createCliProvisionerNameGuard', () => {
expect(keep('bob-provisioner')).toBe(false); expect(keep('bob-provisioner')).toBe(false);
}); });
it('keeps provisioner names when the base member is absent', () => { it('drops provisioner names even when the base member is absent', () => {
const keep = createCliProvisionerNameGuard(['carol-provisioner']); const keep = createCliProvisionerNameGuard(['carol-provisioner']);
expect(keep('carol-provisioner')).toBe(true); expect(keep('carol-provisioner')).toBe(false);
}); });
it('treats base-name collisions case-insensitively', () => { it('treats base-name collisions case-insensitively', () => {