maestro/ui/src/lib/splitPieces.ts
2026-06-04 03:03:12 +00:00

58 lines
1.8 KiB
TypeScript

import type { PieceSummary } from '../api';
export interface SplitPieces {
defaults: PieceSummary[];
customs: PieceSummary[];
}
/**
* Split a flat list of PieceSummary into Default (built-in) and Custom sections.
* Pieces with source === 'builtin' go to defaults; all others go to customs.
*/
export function splitPieces(pieces: PieceSummary[]): SplitPieces {
const defaults: PieceSummary[] = [];
const customs: PieceSummary[] = [];
for (const p of pieces) {
if (p.source === 'builtin') {
defaults.push(p);
} else {
customs.push(p);
}
}
return { defaults, customs };
}
/**
* De-duplicate a flat list of PieceSummary by name, keeping the highest-priority
* source for each name — mirroring the executor's resolution order:
* user-custom > global-custom > builtin
*
* Use this wherever a piece is SELECTED TO RUN (task creation, scheduled tasks,
* continue-with-piece dialogs). The result matches what the executor will actually
* run, so the user sees exactly one "chat" entry rather than two.
*
* Do NOT use this in the management/PiecesPage view — that view intentionally
* shows both the builtin and any same-named custom side-by-side.
*/
export function resolvePieceOptions(pieces: PieceSummary[]): PieceSummary[] {
const PRIORITY: Record<string, number> = {
'user-custom': 0,
'global-custom': 1,
builtin: 2,
};
const seen = new Map<string, PieceSummary>();
for (const p of pieces) {
const existing = seen.get(p.name);
const pPrio = PRIORITY[p.source ?? 'builtin'] ?? 2;
if (!existing) {
seen.set(p.name, p);
} else {
const existingPrio = PRIORITY[existing.source ?? 'builtin'] ?? 2;
if (pPrio < existingPrio) {
seen.set(p.name, p);
}
}
}
return Array.from(seen.values());
}