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.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(
(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(
payload: ListTeamsPayload
): Promise<{ teams: unknown[]; diag: ListTeamsDiag }> {
@ -428,6 +448,7 @@ async function listTeams(
}
dropCliAutoSuffixedMembers(memberMap);
dropCliProvisionerMembers(memberMap);
const members = Array.from(memberMap.values());
const summary = {

View file

@ -43,27 +43,20 @@ const PROVISIONER_SUFFIX = '-provisioner';
/**
* Claude CLI creates temporary "{name}-provisioner" agents during team provisioning
* to spawn real teammates. These are internal artifacts and should be hidden when
* the real base member (e.g. "alice") also exists.
* to spawn real teammates. These are always internal artifacts never real teammates.
*
* Only removes "alice-provisioner" if "alice" is present if the base is missing,
* the provisioner entry is kept for visibility.
* Unlike numeric suffixes (alice-2) which can be intentional, "-provisioner" is a
* 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(
allNames: Iterable<string>
_allNames: Iterable<string>
): (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 => {
const lower = name.trim().toLowerCase();
if (!lower.endsWith(PROVISIONER_SUFFIX)) return true;
const base = lower.slice(0, -PROVISIONER_SUFFIX.length);
if (!base) return true;
return !allLower.has(base);
// Keep bare "-provisioner" (no base) — that's not a CLI artifact pattern
return !base;
};
}

View file

@ -56,10 +56,10 @@ describe('createCliProvisionerNameGuard', () => {
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']);
expect(keep('carol-provisioner')).toBe(true);
expect(keep('carol-provisioner')).toBe(false);
});
it('treats base-name collisions case-insensitively', () => {