Files
openmaic/OpenMAIC/lib/edit/agent-roster.ts
2026-08-16 14:58:47 +08:00

60 lines
2.6 KiB
TypeScript

import type { Stage, GeneratedAgentConfig } from '@/lib/types/stage';
import { createAgentConfig, type AgentRoster } from '@/lib/edit/agent-ops';
/**
* Resolve the editable agent roster for a stage.
*
* Precedence:
* 1. `stage.generatedAgentConfigs` is present and non-empty → return as-is.
* 2. `stage.agentIds` is present → map each id through `resolvePreset(id)`,
* dropping ids that resolve to `undefined`.
* 3. If the result so far is empty → return `[ createAgentConfig('teacher', 0, makeId()) ]`.
*
* The returned roster always contains ≥1 teacher. If step 2 yields configs
* but none carry role 'teacher', a default teacher is prepended.
*/
export function materializeRoster(
stage: Pick<Stage, 'agentIds' | 'generatedAgentConfigs'>,
resolvePreset: (id: string) => GeneratedAgentConfig | undefined,
makeId: () => string,
isGlobalDefault: (id: string) => boolean = () => false,
): AgentRoster {
// Step 1 — already materialized.
// Deliberately returns the array as-is (by reference, no defensive copy) and does NOT
// enforce the ≥1-teacher invariant: a stored roster is already maintained by the
// editor's last-teacher guard, and callers rely on reference identity for change detection.
if (stage.generatedAgentConfigs && stage.generatedAgentConfigs.length > 0) {
return stage.generatedAgentConfigs;
}
// Step 2 — resolve preset ids. Global default presets get a fresh stage-scoped
// id (prevents the first roster edit from shadowing built-in defaults in the
// registry with stage-bound generated copies). Stage-generated ids are kept
// as-is so existing scene references (scene.multiAgent.agentIds) remain valid
// after a roster edit.
let roster: AgentRoster = [];
if (stage.agentIds && stage.agentIds.length > 0) {
roster = stage.agentIds
.map((id) => {
const cfg = resolvePreset(id);
if (!cfg) return undefined;
return isGlobalDefault(id) ? { ...cfg, id: makeId() } : { ...cfg };
})
.filter((cfg): cfg is GeneratedAgentConfig => cfg !== undefined);
}
// Step 3 — fallback to a single default teacher when nothing was resolved
if (roster.length === 0) {
return [createAgentConfig('teacher', 0, makeId())];
}
// Guarantee ≥1 teacher — prepend a default teacher when none present.
// Use roster.length as the palette index so avatar/color cycling continues correctly.
const hasTeacher = roster.some((a) => a.role === 'teacher');
if (!hasTeacher) {
return [createAgentConfig('teacher', roster.length, makeId()), ...roster];
}
return roster;
}