agenthub/src/core/services/identityService.ts
chahinebrini 6d088648da feat(hub): Korrektheits- und Realtime-Härtung — Alias, Lifecycle, Presence, Architekten-Pulse (TSK-0242/0245/0249)
Drei vom Architekten abgenommene Tasks, gebündelt als Checkpoint:

- TSK-0242: Agent-Alias-Mapping (kimi-ah → kimi kanonisiert, Rollen → preferredAgent),
  reopenTask räumt claimedBy ab, fsWatch reindiziert direkte Datei-Edits,
  work-Default 300s → 50s, task_list mit Limit.
- TSK-0245: zwei Agent-Klassen (dispatch loop|architect). Watchdog mahnt
  architekt-getriebene Agenten nur noch EINMAL statt im Minutentakt;
  `task dispatch` startet sie explizit, `task record` trägt extern
  erledigte Arbeit mit origin=external nach.
- TSK-0249: Lifecycle wird serverseitig erzwungen (open→review scheitert mit
  klarer Meldung), claimedBy/doneBy überleben bis done, Presence pro Agent,
  Review-Watchdog, GET /architect/pulse (1.4 kB statt 34 kB, since-Cursor,
  omitted statt stillem Abschneiden), unbekannter Agent → 400 statt 500.

Alle Punkte live am laufenden Hub nachgemessen, nicht aus Agenten-Logs übernommen.
Tests: 242 → 279 grün, tsc sauber.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 17:48:24 +02:00

50 lines
1.7 KiB
TypeScript

import { loadConfig } from '../config.js';
export interface AgentIdentity {
canonical: string;
names: Set<string>;
}
/**
* Resolve an agent, configured alias, or role name to the canonical roster
* agent. Projects without an explicit roster keep accepting free-form agent
* names for backward compatibility.
*/
export function resolveAgentName(cwd: string, recipient: string): string {
const raw = recipient?.trim();
if (!raw) throw new Error('Agent recipient is required');
const key = raw.toLowerCase();
let config;
try {
config = loadConfig(cwd);
} catch {
return raw;
}
const role = Object.entries(config.roles).find(([name]) => name.toLowerCase() === key);
if (role) return role[1].preferredAgent;
const agents = config.agents;
if (!agents || Object.keys(agents).length === 0) return raw;
for (const [canonical, entry] of Object.entries(agents)) {
if (canonical.toLowerCase() === key || entry.aliases.some((alias) => alias.toLowerCase() === key)) {
return canonical;
}
}
throw new Error(`Unknown agent, alias, or role: "${raw}"`);
}
/** All names that should compare equal for task/handoff/inbox matching. */
export function agentIdentity(cwd: string, recipient: string): AgentIdentity {
const canonical = resolveAgentName(cwd, recipient);
const config = loadConfig(cwd);
const names = new Set<string>([canonical.toLowerCase()]);
const entry = config.agents?.[canonical];
for (const alias of entry?.aliases ?? []) names.add(alias.toLowerCase());
for (const [role, roleConfig] of Object.entries(config.roles)) {
if (roleConfig.preferredAgent.toLowerCase() === canonical.toLowerCase()) names.add(role.toLowerCase());
}
return { canonical, names };
}