124 lines
4.3 KiB
JavaScript
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();
|