Files
doormilxpress_astryx/src/lib/assistant/skills/SkillRegistry.js

124 lines
4.3 KiB
JavaScript

// ==============================|| 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();