// ==============================|| Doormile AI — Skill Registry ||============================== // // // The console's eight monitoring skills: their RULES live in code // (./definitions), their SETTINGS — enabled, thresholds — live in the backend // agent registry (/admin/ai/skills, edited in Settings → Skills & Tools). This // class joins the two. // // On the feat/agentic-ops-layer branch these settings were kept in the // browser's localStorage, so a threshold tuned on one machine changed nothing // on any other, and two operators could look at the same board through // different rules. Ported onto main in Phase 3 of docs/agent-platform-plan.md, // the settings come from the registry instead, and nothing is stored locally. // // Until the registry answers (first load, a network error, a partner login // that is not allowed to read it) every skill runs on its code defaults, and // `getSource()` says so, so the banner can tell the operator which settings it // is running on rather than implying they are the tuned ones. import { SlaGuardianSkill } from './definitions/SlaGuardianSkill'; import { DoorstepStallSkill } from './definitions/DoorstepStallSkill'; import { FleetBalancerSkill } from './definitions/FleetBalancerSkill'; import { HighValueCodSkill } from './definitions/HighValueCodSkill'; import { RiderBatterySafetySkill } from './definitions/RiderBatterySafetySkill'; import { HubCongestionSkill } from './definitions/HubCongestionSkill'; import { LateDispatchSkill } from './definitions/LateDispatchSkill'; import { CashExposureSkill } from './definitions/CashExposureSkill'; export const DEFAULT_SKILLS = [ SlaGuardianSkill, DoorstepStallSkill, FleetBalancerSkill, HighValueCodSkill, RiderBatterySafetySkill, HubCongestionSkill, LateDispatchSkill, CashExposureSkill ]; /** A stored value outside the definition's range falls back to its default. */ const inRange = (config, v) => typeof v === 'number' && Number.isFinite(v) && v >= config.min && v <= config.max; class SkillRegistryClass { constructor() { this.skills = new Map(); this.listeners = new Set(); this.config = {}; // { [skillId]: { enabled: boolean, thresholds: { [key]: number } } } this.source = 'defaults'; DEFAULT_SKILLS.forEach((skill) => this.skills.set(skill.id, skill)); } /** * Adopt the registry's settings. `rows` is the /admin/ai/skills response; * rows for skills this console does not implement are ignored. */ applyRegistryConfig(rows) { const next = {}; (Array.isArray(rows) ? rows : []).forEach((row) => { if (!row || !this.skills.has(row.skillid)) return; next[row.skillid] = { enabled: Boolean(row.enabled), thresholds: row.thresholds && typeof row.thresholds === 'object' ? row.thresholds : {} }; }); this.config = next; this.source = 'registry'; this.notify(); } /** Forget the registry's settings and run on code defaults. */ clearRegistryConfig() { if (this.source === 'defaults' && !Object.keys(this.config).length) return; this.config = {}; this.source = 'defaults'; this.notify(); } /** 'registry' when running on the tuned settings, 'defaults' otherwise. */ getSource() { return this.source; } getAllSkills() { return Array.from(this.skills.values()).map((skill) => { const cfg = this.config[skill.id]; const enabled = cfg ? cfg.enabled : (skill.defaultEnabled ?? true); const effectiveThresholds = {}; Object.entries(skill.thresholds || {}).forEach(([key, config]) => { const stored = cfg?.thresholds?.[key]; effectiveThresholds[key] = { ...config, currentValue: inRange(config, stored) ? stored : config.value }; }); return { ...skill, enabled, effectiveThresholds }; }); } getActiveSkills() { return this.getAllSkills().filter((s) => s.enabled); } getSkill(skillId) { return this.getAllSkills().find((s) => s.id === skillId); } subscribe(listener) { this.listeners.add(listener); return () => this.listeners.delete(listener); } notify() { const all = this.getAllSkills(); this.listeners.forEach((fn) => { try { fn(all); } catch { // One listener failing must not stop the others hearing the change. } }); } } export const SkillRegistry = new SkillRegistryClass();