Add operator-subset knowledge base wiring for Netx Ops P1.

Locate and validate MANIFEST under kbRoot, show anti-mixup status in settings, and inject KB_* env plus kb-context so sessions degrade to pure netx when unset.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-09-13 10:39:28 +08:00
parent e6f9d062ed
commit de7c6a0c4b
23 changed files with 2694 additions and 68 deletions

View file

@ -215,22 +215,120 @@ async function registerGroupSkills(ctx, groupIds, providerLabel) {
};
}
// src/netx/runtime.ts
var STORE_KEY = Symbol.for("dsh-netxops.connection-store");
// src/netx/kb-context-skill.ts
var SKILL_NAME = "kb-context";
function skillBody(snapshot) {
if (snapshot.status === "configured") {
const flags = Object.entries(snapshot.content).filter(([, on]) => on).map(([key]) => key).join(", ") || "(none)";
return {
description: "Operator knowledge-base context for this Host (paths + identity from MANIFEST).",
content: [
"## Knowledge base (configured)",
"",
"When troubleshooting with operator playbooks or docs, **compose paths from this root**.",
"Do not invent another operator or country.",
"",
`| Field | Value |`,
`| --- | --- |`,
`| kbStatus | configured |`,
`| kbRoot | \`${snapshot.realRoot}\` |`,
`| kbOperator | ${snapshot.operatorName} |`,
`| kbCountry | ${snapshot.country} |`,
`| kbVersion | ${snapshot.version} |`,
`| kbContent (on) | ${flags} |`,
"",
"Environment mirrors: `KB_ROOT`, `KB_OPERATOR`, `KB_COUNTRY`, `KB_VERSION`, `KB_CONTENT`, `KB_STATUS`."
].join(`
`)
};
}
const reason = snapshot.status === "error" ? snapshot.errorMessage || "invalid knowledge base" : "kbRoot is empty";
return {
description: "Knowledge-base context is unavailable — stay on pure netx evidence.",
content: [
"## Knowledge base (unavailable)",
"",
`kbStatus=\`${snapshot.status}\`. ${reason}`,
"",
"**Do not invent an operator, country, or KB paths.**",
"Use only netx tools / live evidence (alarms, inventory, CLI, topology).",
"Operator-specific playbooks are out of scope until a valid MANIFEST is configured."
].join(`
`)
};
}
function registerKbContextSkill(ctx, snapshot) {
const skillsApi = ctx.skills;
if (!skillsApi || typeof skillsApi.register !== "function") {
return () => {};
}
const body = skillBody(snapshot);
return skillsApi.register({
name: SKILL_NAME,
description: body.description,
content: body.content,
provider: "netxops-kb",
source: "custom"
});
}
// src/netx/kb-manifest.ts
var EMPTY_CONTENT = {
regions: false,
theory: false,
packet: false,
skills: false
};
function unconfiguredKbSnapshot() {
return {
status: "unconfigured",
realRoot: "",
operatorName: "",
country: "",
version: "",
content: { ...EMPTY_CONTENT },
errorMessage: ""
};
}
// src/netx/kb-runtime.ts
var STORE_KEY = Symbol.for("dsh-netxops.kb-store");
function store() {
const root = globalThis;
let current = root[STORE_KEY];
if (current === undefined) {
current = { connection: undefined, listeners: new Set };
current = { snapshot: unconfiguredKbSnapshot(), listeners: new Set };
root[STORE_KEY] = current;
}
return current;
}
function getKbContext() {
return { ...store().snapshot, content: { ...store().snapshot.content } };
}
function watchKbContext(listener) {
const state = store();
state.listeners.add(listener);
return () => {
state.listeners.delete(listener);
};
}
// src/netx/runtime.ts
var STORE_KEY2 = Symbol.for("dsh-netxops.connection-store");
function store2() {
const root = globalThis;
let current = root[STORE_KEY2];
if (current === undefined) {
current = { connection: undefined, listeners: new Set };
root[STORE_KEY2] = current;
}
return current;
}
function getNetxConnection() {
return store().connection;
return store2().connection;
}
function watchNetxConnection(listener) {
const state = store();
const state = store2();
state.listeners.add(listener);
return () => {
state.listeners.delete(listener);
@ -1749,6 +1847,7 @@ function applyGroupToolsPlugin(ctx, options) {
remountTools();
});
ctx.inject(["skills"], (skillsCtx) => {
let unregisterKbSkill;
const remountSkills = () => {
const gen = ++skillGeneration;
unregisterSkills?.();
@ -1766,15 +1865,27 @@ function applyGroupToolsPlugin(ctx, options) {
skillsCtx.logger.warn("%s: skill register failed: %s", options.name, error);
});
};
const remountKbSkill = () => {
unregisterKbSkill?.();
unregisterKbSkill = undefined;
unregisterKbSkill = registerKbContextSkill(skillsCtx, getKbContext());
};
remountSkills();
remountKbSkill();
const stopSkillWatch = watchNetxConnection(() => {
remountSkills();
});
const stopKbWatch = watchKbContext(() => {
remountKbSkill();
});
skillsCtx.effect(() => () => {
skillGeneration += 1;
stopSkillWatch();
stopKbWatch();
unregisterSkills?.();
unregisterSkills = undefined;
unregisterKbSkill?.();
unregisterKbSkill = undefined;
}, `${options.name}: dispose skills`);
});
ctx.effect(() => () => {