Files
gori-agent/src/roles/role-registry.ts
T
2026-08-16 10:08:03 +08:00

52 lines
1.9 KiB
TypeScript

import crypto from "node:crypto";
import type { AppConfig, RoleConfig } from "../config.js";
import { SkillLoader, type LoadedSkill } from "./skill-loader.js";
export interface ResolvedRole extends RoleConfig {
loadedSkills: LoadedSkill[];
fingerprint: string;
bootstrap: string;
}
export class RoleRegistry {
private readonly roles = new Map<string, ResolvedRole>();
constructor(private readonly config: AppConfig) {
const loader = new SkillLoader(config.skills);
for (const role of config.roles) {
const loadedSkills = role.skills.map((id) => loader.load(id));
const fingerprint = crypto.createHash("sha256").update(JSON.stringify({
id: role.id,
backend: role.backend,
workspace: role.workspace,
persona: role.persona,
policy: role.policy,
skills: loadedSkills.map(({ id, file, hash }) => ({ id, file, hash }))
})).digest("hex");
this.roles.set(role.id, { ...role, loadedSkills, fingerprint, bootstrap: buildBootstrap(role, loadedSkills) });
}
}
get(id?: string): ResolvedRole {
const roleId = id || this.config.defaultRole;
const role = this.roles.get(roleId);
if (!role) throw new Error(`Unknown role: ${roleId}`);
return role;
}
has(id: string): boolean { return this.roles.has(id); }
list(): string[] { return [...this.roles.keys()].sort(); }
defaultId(): string { return this.config.defaultRole; }
}
function buildBootstrap(role: RoleConfig, skills: LoadedSkill[]): string {
return [
"Initialize this ACP session with the following role. Treat these instructions as persistent context. Reply only with READY.",
`Role: ${role.id}`,
`Workspace: ${role.workspace}`,
role.persona ? `Persona:\n${role.persona}` : "Persona: general coding assistant",
`Permission policy enforced by the ACP client: ${JSON.stringify(role.policy)}`,
...skills.map((skill) => `Skill ${skill.id} (${skill.file}):\n${skill.content}`)
].join("\n\n");
}