mirror of
https://github.com/hansjone/netxops.git
synced 2026-10-10 01:43:16 +08:00
Register MANIFEST paths.localSkills alongside pack _skills.
Parse packaging-generated paths and load each local_skills/*/SKILL.md with the same hasSkills toggles and provider as shared packs. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
6dee69be31
commit
53c47fbc6a
18 changed files with 662 additions and 156 deletions
|
|
@ -240,7 +240,8 @@ function skillBody(snapshot) {
|
|||
"Environment mirrors: KB_ROOT, KB_OPERATOR, KB_COUNTRY, KB_VERSION, KB_CONTENT, KB_STATUS.",
|
||||
"",
|
||||
"Business playbooks (kb-troubleshoot, kb-retrieve, …) register from",
|
||||
`${snapshot.realRoot}/_skills/ when hasSkills is true — use those skills for KB triage;`,
|
||||
`${snapshot.realRoot}/_skills/ (or MANIFEST paths.skills) when hasSkills is true,`,
|
||||
"plus paths.localSkills when present — use those skills for KB triage;",
|
||||
"keep netx-ops for live netx evidence only."
|
||||
].join(`
|
||||
`)
|
||||
|
|
@ -282,24 +283,29 @@ import { join as join2 } from "node:path";
|
|||
function kbPackSkillsEnabled(snapshot) {
|
||||
return snapshot.status === "configured" && snapshot.content.hasSkills === true;
|
||||
}
|
||||
async function registerKbPackSkills(ctx, snapshot, options) {
|
||||
if (!options.enabled || !kbPackSkillsEnabled(snapshot)) {
|
||||
return () => {};
|
||||
}
|
||||
const skillsApi = ctx.skills;
|
||||
if (!skillsApi || typeof skillsApi.register !== "function") {
|
||||
return () => {};
|
||||
}
|
||||
const root = join2(snapshot.realRoot, "_skills");
|
||||
function resolveKbSkillsRoot(snapshot) {
|
||||
const rel = snapshot.paths.skills?.trim() || "_skills";
|
||||
return join2(snapshot.realRoot, rel);
|
||||
}
|
||||
function resolveKbLocalSkillsRoot(snapshot) {
|
||||
const rel = snapshot.paths.localSkills?.trim();
|
||||
if (!rel)
|
||||
return null;
|
||||
return join2(snapshot.realRoot, rel);
|
||||
}
|
||||
async function registerSkillsUnderRoot(ctx, skillsApi, root, provider, options) {
|
||||
let entries;
|
||||
try {
|
||||
entries = await readdir2(root);
|
||||
} catch {
|
||||
ctx.logger.info("netxops: kb pack skills dir missing at %s — skip", root);
|
||||
return () => {};
|
||||
if (options.logMissing) {
|
||||
ctx.logger.info("netxops: kb %s dir missing at %s — skip", options.label, root);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
if (entries.length === 0)
|
||||
return [];
|
||||
const disposers = [];
|
||||
const provider = options.providerLabel ?? "netxops-kb-pack";
|
||||
for (const entry of entries) {
|
||||
const full = join2(root, entry);
|
||||
let isDir = false;
|
||||
|
|
@ -314,11 +320,11 @@ async function registerKbPackSkills(ctx, snapshot, options) {
|
|||
try {
|
||||
skill = await loadSkillBundle(full);
|
||||
} catch (error) {
|
||||
ctx.logger.warn("netxops: kb pack skill load failed at %s: %s", full, error instanceof Error ? error.message : String(error));
|
||||
ctx.logger.warn("netxops: kb %s skill load failed at %s: %s", options.label, full, error instanceof Error ? error.message : String(error));
|
||||
continue;
|
||||
}
|
||||
if (!skill) {
|
||||
ctx.logger.warn("netxops: kb pack skill skipped (invalid SKILL.md): %s", full);
|
||||
ctx.logger.warn("netxops: kb %s skill skipped (invalid SKILL.md): %s", options.label, full);
|
||||
continue;
|
||||
}
|
||||
disposers.push(skillsApi.register({
|
||||
|
|
@ -331,8 +337,32 @@ async function registerKbPackSkills(ctx, snapshot, options) {
|
|||
source: "custom"
|
||||
}));
|
||||
}
|
||||
return disposers;
|
||||
}
|
||||
async function registerKbPackSkills(ctx, snapshot, options) {
|
||||
if (!options.enabled || !kbPackSkillsEnabled(snapshot)) {
|
||||
return () => {};
|
||||
}
|
||||
const skillsApi = ctx.skills;
|
||||
if (!skillsApi || typeof skillsApi.register !== "function") {
|
||||
return () => {};
|
||||
}
|
||||
const provider = options.providerLabel ?? "netxops-kb-pack";
|
||||
const disposers = [];
|
||||
const skillsRoot = resolveKbSkillsRoot(snapshot);
|
||||
disposers.push(...await registerSkillsUnderRoot(ctx, skillsApi, skillsRoot, provider, {
|
||||
logMissing: true,
|
||||
label: "pack"
|
||||
}));
|
||||
const localRoot = resolveKbLocalSkillsRoot(snapshot);
|
||||
if (localRoot) {
|
||||
disposers.push(...await registerSkillsUnderRoot(ctx, skillsApi, localRoot, provider, {
|
||||
logMissing: false,
|
||||
label: "local"
|
||||
}));
|
||||
}
|
||||
if (disposers.length > 0) {
|
||||
ctx.logger.info("netxops: kb pack skills registered count=%s root=%s provider=%s", disposers.length, root, provider);
|
||||
ctx.logger.info("netxops: kb pack skills registered count=%s skillsRoot=%s localSkills=%s provider=%s", disposers.length, skillsRoot, localRoot ?? "(none)", provider);
|
||||
}
|
||||
return () => {
|
||||
for (const dispose of disposers)
|
||||
|
|
@ -356,6 +386,7 @@ function unconfiguredKbSnapshot() {
|
|||
country: "",
|
||||
version: "",
|
||||
content: { ...EMPTY_CONTENT },
|
||||
paths: {},
|
||||
errorMessage: ""
|
||||
};
|
||||
}
|
||||
|
|
@ -372,7 +403,12 @@ function store() {
|
|||
return current;
|
||||
}
|
||||
function getKbContext() {
|
||||
return { ...store().snapshot, content: { ...store().snapshot.content } };
|
||||
const snap = store().snapshot;
|
||||
return {
|
||||
...snap,
|
||||
content: { ...snap.content },
|
||||
paths: { ...snap.paths }
|
||||
};
|
||||
}
|
||||
function watchKbContext(listener) {
|
||||
const state = store();
|
||||
|
|
|
|||
|
|
@ -240,7 +240,8 @@ function skillBody(snapshot) {
|
|||
"Environment mirrors: KB_ROOT, KB_OPERATOR, KB_COUNTRY, KB_VERSION, KB_CONTENT, KB_STATUS.",
|
||||
"",
|
||||
"Business playbooks (kb-troubleshoot, kb-retrieve, …) register from",
|
||||
`${snapshot.realRoot}/_skills/ when hasSkills is true — use those skills for KB triage;`,
|
||||
`${snapshot.realRoot}/_skills/ (or MANIFEST paths.skills) when hasSkills is true,`,
|
||||
"plus paths.localSkills when present — use those skills for KB triage;",
|
||||
"keep netx-ops for live netx evidence only."
|
||||
].join(`
|
||||
`)
|
||||
|
|
@ -282,24 +283,29 @@ import { join as join2 } from "node:path";
|
|||
function kbPackSkillsEnabled(snapshot) {
|
||||
return snapshot.status === "configured" && snapshot.content.hasSkills === true;
|
||||
}
|
||||
async function registerKbPackSkills(ctx, snapshot, options) {
|
||||
if (!options.enabled || !kbPackSkillsEnabled(snapshot)) {
|
||||
return () => {};
|
||||
}
|
||||
const skillsApi = ctx.skills;
|
||||
if (!skillsApi || typeof skillsApi.register !== "function") {
|
||||
return () => {};
|
||||
}
|
||||
const root = join2(snapshot.realRoot, "_skills");
|
||||
function resolveKbSkillsRoot(snapshot) {
|
||||
const rel = snapshot.paths.skills?.trim() || "_skills";
|
||||
return join2(snapshot.realRoot, rel);
|
||||
}
|
||||
function resolveKbLocalSkillsRoot(snapshot) {
|
||||
const rel = snapshot.paths.localSkills?.trim();
|
||||
if (!rel)
|
||||
return null;
|
||||
return join2(snapshot.realRoot, rel);
|
||||
}
|
||||
async function registerSkillsUnderRoot(ctx, skillsApi, root, provider, options) {
|
||||
let entries;
|
||||
try {
|
||||
entries = await readdir2(root);
|
||||
} catch {
|
||||
ctx.logger.info("netxops: kb pack skills dir missing at %s — skip", root);
|
||||
return () => {};
|
||||
if (options.logMissing) {
|
||||
ctx.logger.info("netxops: kb %s dir missing at %s — skip", options.label, root);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
if (entries.length === 0)
|
||||
return [];
|
||||
const disposers = [];
|
||||
const provider = options.providerLabel ?? "netxops-kb-pack";
|
||||
for (const entry of entries) {
|
||||
const full = join2(root, entry);
|
||||
let isDir = false;
|
||||
|
|
@ -314,11 +320,11 @@ async function registerKbPackSkills(ctx, snapshot, options) {
|
|||
try {
|
||||
skill = await loadSkillBundle(full);
|
||||
} catch (error) {
|
||||
ctx.logger.warn("netxops: kb pack skill load failed at %s: %s", full, error instanceof Error ? error.message : String(error));
|
||||
ctx.logger.warn("netxops: kb %s skill load failed at %s: %s", options.label, full, error instanceof Error ? error.message : String(error));
|
||||
continue;
|
||||
}
|
||||
if (!skill) {
|
||||
ctx.logger.warn("netxops: kb pack skill skipped (invalid SKILL.md): %s", full);
|
||||
ctx.logger.warn("netxops: kb %s skill skipped (invalid SKILL.md): %s", options.label, full);
|
||||
continue;
|
||||
}
|
||||
disposers.push(skillsApi.register({
|
||||
|
|
@ -331,8 +337,32 @@ async function registerKbPackSkills(ctx, snapshot, options) {
|
|||
source: "custom"
|
||||
}));
|
||||
}
|
||||
return disposers;
|
||||
}
|
||||
async function registerKbPackSkills(ctx, snapshot, options) {
|
||||
if (!options.enabled || !kbPackSkillsEnabled(snapshot)) {
|
||||
return () => {};
|
||||
}
|
||||
const skillsApi = ctx.skills;
|
||||
if (!skillsApi || typeof skillsApi.register !== "function") {
|
||||
return () => {};
|
||||
}
|
||||
const provider = options.providerLabel ?? "netxops-kb-pack";
|
||||
const disposers = [];
|
||||
const skillsRoot = resolveKbSkillsRoot(snapshot);
|
||||
disposers.push(...await registerSkillsUnderRoot(ctx, skillsApi, skillsRoot, provider, {
|
||||
logMissing: true,
|
||||
label: "pack"
|
||||
}));
|
||||
const localRoot = resolveKbLocalSkillsRoot(snapshot);
|
||||
if (localRoot) {
|
||||
disposers.push(...await registerSkillsUnderRoot(ctx, skillsApi, localRoot, provider, {
|
||||
logMissing: false,
|
||||
label: "local"
|
||||
}));
|
||||
}
|
||||
if (disposers.length > 0) {
|
||||
ctx.logger.info("netxops: kb pack skills registered count=%s root=%s provider=%s", disposers.length, root, provider);
|
||||
ctx.logger.info("netxops: kb pack skills registered count=%s skillsRoot=%s localSkills=%s provider=%s", disposers.length, skillsRoot, localRoot ?? "(none)", provider);
|
||||
}
|
||||
return () => {
|
||||
for (const dispose of disposers)
|
||||
|
|
@ -356,6 +386,7 @@ function unconfiguredKbSnapshot() {
|
|||
country: "",
|
||||
version: "",
|
||||
content: { ...EMPTY_CONTENT },
|
||||
paths: {},
|
||||
errorMessage: ""
|
||||
};
|
||||
}
|
||||
|
|
@ -372,7 +403,12 @@ function store() {
|
|||
return current;
|
||||
}
|
||||
function getKbContext() {
|
||||
return { ...store().snapshot, content: { ...store().snapshot.content } };
|
||||
const snap = store().snapshot;
|
||||
return {
|
||||
...snap,
|
||||
content: { ...snap.content },
|
||||
paths: { ...snap.paths }
|
||||
};
|
||||
}
|
||||
function watchKbContext(listener) {
|
||||
const state = store();
|
||||
|
|
|
|||
|
|
@ -240,7 +240,8 @@ function skillBody(snapshot) {
|
|||
"Environment mirrors: KB_ROOT, KB_OPERATOR, KB_COUNTRY, KB_VERSION, KB_CONTENT, KB_STATUS.",
|
||||
"",
|
||||
"Business playbooks (kb-troubleshoot, kb-retrieve, …) register from",
|
||||
`${snapshot.realRoot}/_skills/ when hasSkills is true — use those skills for KB triage;`,
|
||||
`${snapshot.realRoot}/_skills/ (or MANIFEST paths.skills) when hasSkills is true,`,
|
||||
"plus paths.localSkills when present — use those skills for KB triage;",
|
||||
"keep netx-ops for live netx evidence only."
|
||||
].join(`
|
||||
`)
|
||||
|
|
@ -282,24 +283,29 @@ import { join as join2 } from "node:path";
|
|||
function kbPackSkillsEnabled(snapshot) {
|
||||
return snapshot.status === "configured" && snapshot.content.hasSkills === true;
|
||||
}
|
||||
async function registerKbPackSkills(ctx, snapshot, options) {
|
||||
if (!options.enabled || !kbPackSkillsEnabled(snapshot)) {
|
||||
return () => {};
|
||||
}
|
||||
const skillsApi = ctx.skills;
|
||||
if (!skillsApi || typeof skillsApi.register !== "function") {
|
||||
return () => {};
|
||||
}
|
||||
const root = join2(snapshot.realRoot, "_skills");
|
||||
function resolveKbSkillsRoot(snapshot) {
|
||||
const rel = snapshot.paths.skills?.trim() || "_skills";
|
||||
return join2(snapshot.realRoot, rel);
|
||||
}
|
||||
function resolveKbLocalSkillsRoot(snapshot) {
|
||||
const rel = snapshot.paths.localSkills?.trim();
|
||||
if (!rel)
|
||||
return null;
|
||||
return join2(snapshot.realRoot, rel);
|
||||
}
|
||||
async function registerSkillsUnderRoot(ctx, skillsApi, root, provider, options) {
|
||||
let entries;
|
||||
try {
|
||||
entries = await readdir2(root);
|
||||
} catch {
|
||||
ctx.logger.info("netxops: kb pack skills dir missing at %s — skip", root);
|
||||
return () => {};
|
||||
if (options.logMissing) {
|
||||
ctx.logger.info("netxops: kb %s dir missing at %s — skip", options.label, root);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
if (entries.length === 0)
|
||||
return [];
|
||||
const disposers = [];
|
||||
const provider = options.providerLabel ?? "netxops-kb-pack";
|
||||
for (const entry of entries) {
|
||||
const full = join2(root, entry);
|
||||
let isDir = false;
|
||||
|
|
@ -314,11 +320,11 @@ async function registerKbPackSkills(ctx, snapshot, options) {
|
|||
try {
|
||||
skill = await loadSkillBundle(full);
|
||||
} catch (error) {
|
||||
ctx.logger.warn("netxops: kb pack skill load failed at %s: %s", full, error instanceof Error ? error.message : String(error));
|
||||
ctx.logger.warn("netxops: kb %s skill load failed at %s: %s", options.label, full, error instanceof Error ? error.message : String(error));
|
||||
continue;
|
||||
}
|
||||
if (!skill) {
|
||||
ctx.logger.warn("netxops: kb pack skill skipped (invalid SKILL.md): %s", full);
|
||||
ctx.logger.warn("netxops: kb %s skill skipped (invalid SKILL.md): %s", options.label, full);
|
||||
continue;
|
||||
}
|
||||
disposers.push(skillsApi.register({
|
||||
|
|
@ -331,8 +337,32 @@ async function registerKbPackSkills(ctx, snapshot, options) {
|
|||
source: "custom"
|
||||
}));
|
||||
}
|
||||
return disposers;
|
||||
}
|
||||
async function registerKbPackSkills(ctx, snapshot, options) {
|
||||
if (!options.enabled || !kbPackSkillsEnabled(snapshot)) {
|
||||
return () => {};
|
||||
}
|
||||
const skillsApi = ctx.skills;
|
||||
if (!skillsApi || typeof skillsApi.register !== "function") {
|
||||
return () => {};
|
||||
}
|
||||
const provider = options.providerLabel ?? "netxops-kb-pack";
|
||||
const disposers = [];
|
||||
const skillsRoot = resolveKbSkillsRoot(snapshot);
|
||||
disposers.push(...await registerSkillsUnderRoot(ctx, skillsApi, skillsRoot, provider, {
|
||||
logMissing: true,
|
||||
label: "pack"
|
||||
}));
|
||||
const localRoot = resolveKbLocalSkillsRoot(snapshot);
|
||||
if (localRoot) {
|
||||
disposers.push(...await registerSkillsUnderRoot(ctx, skillsApi, localRoot, provider, {
|
||||
logMissing: false,
|
||||
label: "local"
|
||||
}));
|
||||
}
|
||||
if (disposers.length > 0) {
|
||||
ctx.logger.info("netxops: kb pack skills registered count=%s root=%s provider=%s", disposers.length, root, provider);
|
||||
ctx.logger.info("netxops: kb pack skills registered count=%s skillsRoot=%s localSkills=%s provider=%s", disposers.length, skillsRoot, localRoot ?? "(none)", provider);
|
||||
}
|
||||
return () => {
|
||||
for (const dispose of disposers)
|
||||
|
|
@ -356,6 +386,7 @@ function unconfiguredKbSnapshot() {
|
|||
country: "",
|
||||
version: "",
|
||||
content: { ...EMPTY_CONTENT },
|
||||
paths: {},
|
||||
errorMessage: ""
|
||||
};
|
||||
}
|
||||
|
|
@ -372,7 +403,12 @@ function store() {
|
|||
return current;
|
||||
}
|
||||
function getKbContext() {
|
||||
return { ...store().snapshot, content: { ...store().snapshot.content } };
|
||||
const snap = store().snapshot;
|
||||
return {
|
||||
...snap,
|
||||
content: { ...snap.content },
|
||||
paths: { ...snap.paths }
|
||||
};
|
||||
}
|
||||
function watchKbContext(listener) {
|
||||
const state = store();
|
||||
|
|
|
|||
|
|
@ -240,7 +240,8 @@ function skillBody(snapshot) {
|
|||
"Environment mirrors: KB_ROOT, KB_OPERATOR, KB_COUNTRY, KB_VERSION, KB_CONTENT, KB_STATUS.",
|
||||
"",
|
||||
"Business playbooks (kb-troubleshoot, kb-retrieve, …) register from",
|
||||
`${snapshot.realRoot}/_skills/ when hasSkills is true — use those skills for KB triage;`,
|
||||
`${snapshot.realRoot}/_skills/ (or MANIFEST paths.skills) when hasSkills is true,`,
|
||||
"plus paths.localSkills when present — use those skills for KB triage;",
|
||||
"keep netx-ops for live netx evidence only."
|
||||
].join(`
|
||||
`)
|
||||
|
|
@ -282,24 +283,29 @@ import { join as join2 } from "node:path";
|
|||
function kbPackSkillsEnabled(snapshot) {
|
||||
return snapshot.status === "configured" && snapshot.content.hasSkills === true;
|
||||
}
|
||||
async function registerKbPackSkills(ctx, snapshot, options) {
|
||||
if (!options.enabled || !kbPackSkillsEnabled(snapshot)) {
|
||||
return () => {};
|
||||
}
|
||||
const skillsApi = ctx.skills;
|
||||
if (!skillsApi || typeof skillsApi.register !== "function") {
|
||||
return () => {};
|
||||
}
|
||||
const root = join2(snapshot.realRoot, "_skills");
|
||||
function resolveKbSkillsRoot(snapshot) {
|
||||
const rel = snapshot.paths.skills?.trim() || "_skills";
|
||||
return join2(snapshot.realRoot, rel);
|
||||
}
|
||||
function resolveKbLocalSkillsRoot(snapshot) {
|
||||
const rel = snapshot.paths.localSkills?.trim();
|
||||
if (!rel)
|
||||
return null;
|
||||
return join2(snapshot.realRoot, rel);
|
||||
}
|
||||
async function registerSkillsUnderRoot(ctx, skillsApi, root, provider, options) {
|
||||
let entries;
|
||||
try {
|
||||
entries = await readdir2(root);
|
||||
} catch {
|
||||
ctx.logger.info("netxops: kb pack skills dir missing at %s — skip", root);
|
||||
return () => {};
|
||||
if (options.logMissing) {
|
||||
ctx.logger.info("netxops: kb %s dir missing at %s — skip", options.label, root);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
if (entries.length === 0)
|
||||
return [];
|
||||
const disposers = [];
|
||||
const provider = options.providerLabel ?? "netxops-kb-pack";
|
||||
for (const entry of entries) {
|
||||
const full = join2(root, entry);
|
||||
let isDir = false;
|
||||
|
|
@ -314,11 +320,11 @@ async function registerKbPackSkills(ctx, snapshot, options) {
|
|||
try {
|
||||
skill = await loadSkillBundle(full);
|
||||
} catch (error) {
|
||||
ctx.logger.warn("netxops: kb pack skill load failed at %s: %s", full, error instanceof Error ? error.message : String(error));
|
||||
ctx.logger.warn("netxops: kb %s skill load failed at %s: %s", options.label, full, error instanceof Error ? error.message : String(error));
|
||||
continue;
|
||||
}
|
||||
if (!skill) {
|
||||
ctx.logger.warn("netxops: kb pack skill skipped (invalid SKILL.md): %s", full);
|
||||
ctx.logger.warn("netxops: kb %s skill skipped (invalid SKILL.md): %s", options.label, full);
|
||||
continue;
|
||||
}
|
||||
disposers.push(skillsApi.register({
|
||||
|
|
@ -331,8 +337,32 @@ async function registerKbPackSkills(ctx, snapshot, options) {
|
|||
source: "custom"
|
||||
}));
|
||||
}
|
||||
return disposers;
|
||||
}
|
||||
async function registerKbPackSkills(ctx, snapshot, options) {
|
||||
if (!options.enabled || !kbPackSkillsEnabled(snapshot)) {
|
||||
return () => {};
|
||||
}
|
||||
const skillsApi = ctx.skills;
|
||||
if (!skillsApi || typeof skillsApi.register !== "function") {
|
||||
return () => {};
|
||||
}
|
||||
const provider = options.providerLabel ?? "netxops-kb-pack";
|
||||
const disposers = [];
|
||||
const skillsRoot = resolveKbSkillsRoot(snapshot);
|
||||
disposers.push(...await registerSkillsUnderRoot(ctx, skillsApi, skillsRoot, provider, {
|
||||
logMissing: true,
|
||||
label: "pack"
|
||||
}));
|
||||
const localRoot = resolveKbLocalSkillsRoot(snapshot);
|
||||
if (localRoot) {
|
||||
disposers.push(...await registerSkillsUnderRoot(ctx, skillsApi, localRoot, provider, {
|
||||
logMissing: false,
|
||||
label: "local"
|
||||
}));
|
||||
}
|
||||
if (disposers.length > 0) {
|
||||
ctx.logger.info("netxops: kb pack skills registered count=%s root=%s provider=%s", disposers.length, root, provider);
|
||||
ctx.logger.info("netxops: kb pack skills registered count=%s skillsRoot=%s localSkills=%s provider=%s", disposers.length, skillsRoot, localRoot ?? "(none)", provider);
|
||||
}
|
||||
return () => {
|
||||
for (const dispose of disposers)
|
||||
|
|
@ -356,6 +386,7 @@ function unconfiguredKbSnapshot() {
|
|||
country: "",
|
||||
version: "",
|
||||
content: { ...EMPTY_CONTENT },
|
||||
paths: {},
|
||||
errorMessage: ""
|
||||
};
|
||||
}
|
||||
|
|
@ -372,7 +403,12 @@ function store() {
|
|||
return current;
|
||||
}
|
||||
function getKbContext() {
|
||||
return { ...store().snapshot, content: { ...store().snapshot.content } };
|
||||
const snap = store().snapshot;
|
||||
return {
|
||||
...snap,
|
||||
content: { ...snap.content },
|
||||
paths: { ...snap.paths }
|
||||
};
|
||||
}
|
||||
function watchKbContext(listener) {
|
||||
const state = store();
|
||||
|
|
|
|||
|
|
@ -240,7 +240,8 @@ function skillBody(snapshot) {
|
|||
"Environment mirrors: KB_ROOT, KB_OPERATOR, KB_COUNTRY, KB_VERSION, KB_CONTENT, KB_STATUS.",
|
||||
"",
|
||||
"Business playbooks (kb-troubleshoot, kb-retrieve, …) register from",
|
||||
`${snapshot.realRoot}/_skills/ when hasSkills is true — use those skills for KB triage;`,
|
||||
`${snapshot.realRoot}/_skills/ (or MANIFEST paths.skills) when hasSkills is true,`,
|
||||
"plus paths.localSkills when present — use those skills for KB triage;",
|
||||
"keep netx-ops for live netx evidence only."
|
||||
].join(`
|
||||
`)
|
||||
|
|
@ -282,24 +283,29 @@ import { join as join2 } from "node:path";
|
|||
function kbPackSkillsEnabled(snapshot) {
|
||||
return snapshot.status === "configured" && snapshot.content.hasSkills === true;
|
||||
}
|
||||
async function registerKbPackSkills(ctx, snapshot, options) {
|
||||
if (!options.enabled || !kbPackSkillsEnabled(snapshot)) {
|
||||
return () => {};
|
||||
}
|
||||
const skillsApi = ctx.skills;
|
||||
if (!skillsApi || typeof skillsApi.register !== "function") {
|
||||
return () => {};
|
||||
}
|
||||
const root = join2(snapshot.realRoot, "_skills");
|
||||
function resolveKbSkillsRoot(snapshot) {
|
||||
const rel = snapshot.paths.skills?.trim() || "_skills";
|
||||
return join2(snapshot.realRoot, rel);
|
||||
}
|
||||
function resolveKbLocalSkillsRoot(snapshot) {
|
||||
const rel = snapshot.paths.localSkills?.trim();
|
||||
if (!rel)
|
||||
return null;
|
||||
return join2(snapshot.realRoot, rel);
|
||||
}
|
||||
async function registerSkillsUnderRoot(ctx, skillsApi, root, provider, options) {
|
||||
let entries;
|
||||
try {
|
||||
entries = await readdir2(root);
|
||||
} catch {
|
||||
ctx.logger.info("netxops: kb pack skills dir missing at %s — skip", root);
|
||||
return () => {};
|
||||
if (options.logMissing) {
|
||||
ctx.logger.info("netxops: kb %s dir missing at %s — skip", options.label, root);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
if (entries.length === 0)
|
||||
return [];
|
||||
const disposers = [];
|
||||
const provider = options.providerLabel ?? "netxops-kb-pack";
|
||||
for (const entry of entries) {
|
||||
const full = join2(root, entry);
|
||||
let isDir = false;
|
||||
|
|
@ -314,11 +320,11 @@ async function registerKbPackSkills(ctx, snapshot, options) {
|
|||
try {
|
||||
skill = await loadSkillBundle(full);
|
||||
} catch (error) {
|
||||
ctx.logger.warn("netxops: kb pack skill load failed at %s: %s", full, error instanceof Error ? error.message : String(error));
|
||||
ctx.logger.warn("netxops: kb %s skill load failed at %s: %s", options.label, full, error instanceof Error ? error.message : String(error));
|
||||
continue;
|
||||
}
|
||||
if (!skill) {
|
||||
ctx.logger.warn("netxops: kb pack skill skipped (invalid SKILL.md): %s", full);
|
||||
ctx.logger.warn("netxops: kb %s skill skipped (invalid SKILL.md): %s", options.label, full);
|
||||
continue;
|
||||
}
|
||||
disposers.push(skillsApi.register({
|
||||
|
|
@ -331,8 +337,32 @@ async function registerKbPackSkills(ctx, snapshot, options) {
|
|||
source: "custom"
|
||||
}));
|
||||
}
|
||||
return disposers;
|
||||
}
|
||||
async function registerKbPackSkills(ctx, snapshot, options) {
|
||||
if (!options.enabled || !kbPackSkillsEnabled(snapshot)) {
|
||||
return () => {};
|
||||
}
|
||||
const skillsApi = ctx.skills;
|
||||
if (!skillsApi || typeof skillsApi.register !== "function") {
|
||||
return () => {};
|
||||
}
|
||||
const provider = options.providerLabel ?? "netxops-kb-pack";
|
||||
const disposers = [];
|
||||
const skillsRoot = resolveKbSkillsRoot(snapshot);
|
||||
disposers.push(...await registerSkillsUnderRoot(ctx, skillsApi, skillsRoot, provider, {
|
||||
logMissing: true,
|
||||
label: "pack"
|
||||
}));
|
||||
const localRoot = resolveKbLocalSkillsRoot(snapshot);
|
||||
if (localRoot) {
|
||||
disposers.push(...await registerSkillsUnderRoot(ctx, skillsApi, localRoot, provider, {
|
||||
logMissing: false,
|
||||
label: "local"
|
||||
}));
|
||||
}
|
||||
if (disposers.length > 0) {
|
||||
ctx.logger.info("netxops: kb pack skills registered count=%s root=%s provider=%s", disposers.length, root, provider);
|
||||
ctx.logger.info("netxops: kb pack skills registered count=%s skillsRoot=%s localSkills=%s provider=%s", disposers.length, skillsRoot, localRoot ?? "(none)", provider);
|
||||
}
|
||||
return () => {
|
||||
for (const dispose of disposers)
|
||||
|
|
@ -356,6 +386,7 @@ function unconfiguredKbSnapshot() {
|
|||
country: "",
|
||||
version: "",
|
||||
content: { ...EMPTY_CONTENT },
|
||||
paths: {},
|
||||
errorMessage: ""
|
||||
};
|
||||
}
|
||||
|
|
@ -372,7 +403,12 @@ function store() {
|
|||
return current;
|
||||
}
|
||||
function getKbContext() {
|
||||
return { ...store().snapshot, content: { ...store().snapshot.content } };
|
||||
const snap = store().snapshot;
|
||||
return {
|
||||
...snap,
|
||||
content: { ...snap.content },
|
||||
paths: { ...snap.paths }
|
||||
};
|
||||
}
|
||||
function watchKbContext(listener) {
|
||||
const state = store();
|
||||
|
|
|
|||
|
|
@ -583,6 +583,7 @@ function unconfiguredKbSnapshot() {
|
|||
country: "",
|
||||
version: "",
|
||||
content: { ...EMPTY_CONTENT },
|
||||
paths: {},
|
||||
errorMessage: ""
|
||||
};
|
||||
}
|
||||
|
|
@ -604,6 +605,14 @@ function asKbSnapshot(value) {
|
|||
content[key] = flag === true;
|
||||
}
|
||||
}
|
||||
const pathsRaw = row.paths;
|
||||
const paths = {};
|
||||
if (pathsRaw !== null && typeof pathsRaw === "object" && !Array.isArray(pathsRaw)) {
|
||||
for (const [key, value2] of Object.entries(pathsRaw)) {
|
||||
if (typeof value2 === "string" && value2.trim())
|
||||
paths[key] = value2.trim();
|
||||
}
|
||||
}
|
||||
return {
|
||||
status,
|
||||
realRoot: typeof row.realRoot === "string" ? row.realRoot : "",
|
||||
|
|
@ -611,6 +620,7 @@ function asKbSnapshot(value) {
|
|||
country: typeof row.country === "string" ? row.country : "",
|
||||
version: typeof row.version === "string" ? row.version : "",
|
||||
content,
|
||||
paths,
|
||||
errorMessage: typeof row.errorMessage === "string" ? row.errorMessage : ""
|
||||
};
|
||||
}
|
||||
|
|
@ -2282,7 +2292,7 @@ var en = {
|
|||
groupInPreset: "In Netx Ops preset",
|
||||
groupPublic: "Publish to other presets",
|
||||
kbRoot: "Package root",
|
||||
kbRootHint: "Folder with MANIFEST.json (operator-subset v1.0). Browse auto-saves. Pack skills load from _skills/ when hasSkills.",
|
||||
kbRootHint: "Folder with MANIFEST.json (operator-subset v1.0). Browse auto-saves. Pack skills load from paths.skills/_skills and paths.localSkills when hasSkills.",
|
||||
kbBrowse: "Browse…",
|
||||
kbBrowseUnavailable: "Directory picker unavailable — paste an absolute folder path, then Save.",
|
||||
kbBrowseFailed: "Browse failed: {detail}",
|
||||
|
|
@ -2355,7 +2365,7 @@ var zh = {
|
|||
groupInPreset: "在 Netx Ops 预设中启用",
|
||||
groupPublic: "对其他预设公开",
|
||||
kbRoot: "知识包根目录",
|
||||
kbRootHint: "含 MANIFEST.json 的运营商子集包(v1.0)。浏览选目录后会自动保存;hasSkills 时加载 _skills/。",
|
||||
kbRootHint: "含 MANIFEST.json 的运营商子集包(v1.0)。浏览选目录后会自动保存;hasSkills 时加载 paths.skills/_skills 与 paths.localSkills。",
|
||||
kbBrowse: "浏览…",
|
||||
kbBrowseUnavailable: "目录选择器不可用 — 请粘贴绝对目录路径后点保存。",
|
||||
kbBrowseFailed: "浏览失败:{detail}",
|
||||
|
|
|
|||
95
lib/index.js
95
lib/index.js
|
|
@ -810,7 +810,8 @@ function skillBody(snapshot) {
|
|||
"Environment mirrors: KB_ROOT, KB_OPERATOR, KB_COUNTRY, KB_VERSION, KB_CONTENT, KB_STATUS.",
|
||||
"",
|
||||
"Business playbooks (kb-troubleshoot, kb-retrieve, …) register from",
|
||||
`${snapshot.realRoot}/_skills/ when hasSkills is true — use those skills for KB triage;`,
|
||||
`${snapshot.realRoot}/_skills/ (or MANIFEST paths.skills) when hasSkills is true,`,
|
||||
"plus paths.localSkills when present — use those skills for KB triage;",
|
||||
"keep netx-ops for live netx evidence only."
|
||||
].join(`
|
||||
`)
|
||||
|
|
@ -871,6 +872,7 @@ function unconfiguredKbSnapshot() {
|
|||
country: "",
|
||||
version: "",
|
||||
content: { ...EMPTY_CONTENT },
|
||||
paths: {},
|
||||
errorMessage: ""
|
||||
};
|
||||
}
|
||||
|
|
@ -882,6 +884,7 @@ function errorSnapshot(message) {
|
|||
country: "",
|
||||
version: "",
|
||||
content: { ...EMPTY_CONTENT },
|
||||
paths: {},
|
||||
errorMessage: message
|
||||
};
|
||||
}
|
||||
|
|
@ -988,6 +991,23 @@ function parseContent(raw) {
|
|||
}
|
||||
return out;
|
||||
}
|
||||
function parsePaths(raw) {
|
||||
if (raw === undefined || raw === null)
|
||||
return {};
|
||||
if (typeof raw !== "object" || Array.isArray(raw)) {
|
||||
throw new Error("MANIFEST paths must be an object");
|
||||
}
|
||||
const out = {};
|
||||
for (const [key, value] of Object.entries(raw)) {
|
||||
if (typeof value !== "string")
|
||||
continue;
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed)
|
||||
continue;
|
||||
out[key] = trimmed;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function parseManifest(raw) {
|
||||
let data;
|
||||
try {
|
||||
|
|
@ -1014,7 +1034,8 @@ function parseManifest(raw) {
|
|||
operatorName: asNonEmptyString(op.name, "operator.name"),
|
||||
country: asNonEmptyString(op.country, "operator.country"),
|
||||
version: asNonEmptyString(row.version, "version"),
|
||||
content: parseContent(row.content)
|
||||
content: parseContent(row.content),
|
||||
paths: parsePaths(row.paths)
|
||||
};
|
||||
}
|
||||
function resolveKbRoot(kbRoot, maxDepth = 3) {
|
||||
|
|
@ -1061,6 +1082,7 @@ function resolveKbRoot(kbRoot, maxDepth = 3) {
|
|||
country: parsed.country,
|
||||
version: parsed.version,
|
||||
content: parsed.content,
|
||||
paths: { ...parsed.paths },
|
||||
errorMessage: ""
|
||||
};
|
||||
} catch (error) {
|
||||
|
|
@ -1074,24 +1096,29 @@ import { join as join4 } from "node:path";
|
|||
function kbPackSkillsEnabled(snapshot) {
|
||||
return snapshot.status === "configured" && snapshot.content.hasSkills === true;
|
||||
}
|
||||
async function registerKbPackSkills(ctx, snapshot, options) {
|
||||
if (!options.enabled || !kbPackSkillsEnabled(snapshot)) {
|
||||
return () => {};
|
||||
}
|
||||
const skillsApi = ctx.skills;
|
||||
if (!skillsApi || typeof skillsApi.register !== "function") {
|
||||
return () => {};
|
||||
}
|
||||
const root = join4(snapshot.realRoot, "_skills");
|
||||
function resolveKbSkillsRoot(snapshot) {
|
||||
const rel = snapshot.paths.skills?.trim() || "_skills";
|
||||
return join4(snapshot.realRoot, rel);
|
||||
}
|
||||
function resolveKbLocalSkillsRoot(snapshot) {
|
||||
const rel = snapshot.paths.localSkills?.trim();
|
||||
if (!rel)
|
||||
return null;
|
||||
return join4(snapshot.realRoot, rel);
|
||||
}
|
||||
async function registerSkillsUnderRoot(ctx, skillsApi, root, provider, options) {
|
||||
let entries;
|
||||
try {
|
||||
entries = await readdir2(root);
|
||||
} catch {
|
||||
ctx.logger.info("netxops: kb pack skills dir missing at %s — skip", root);
|
||||
return () => {};
|
||||
if (options.logMissing) {
|
||||
ctx.logger.info("netxops: kb %s dir missing at %s — skip", options.label, root);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
if (entries.length === 0)
|
||||
return [];
|
||||
const disposers = [];
|
||||
const provider = options.providerLabel ?? "netxops-kb-pack";
|
||||
for (const entry of entries) {
|
||||
const full = join4(root, entry);
|
||||
let isDir = false;
|
||||
|
|
@ -1106,11 +1133,11 @@ async function registerKbPackSkills(ctx, snapshot, options) {
|
|||
try {
|
||||
skill = await loadSkillBundle(full);
|
||||
} catch (error) {
|
||||
ctx.logger.warn("netxops: kb pack skill load failed at %s: %s", full, error instanceof Error ? error.message : String(error));
|
||||
ctx.logger.warn("netxops: kb %s skill load failed at %s: %s", options.label, full, error instanceof Error ? error.message : String(error));
|
||||
continue;
|
||||
}
|
||||
if (!skill) {
|
||||
ctx.logger.warn("netxops: kb pack skill skipped (invalid SKILL.md): %s", full);
|
||||
ctx.logger.warn("netxops: kb %s skill skipped (invalid SKILL.md): %s", options.label, full);
|
||||
continue;
|
||||
}
|
||||
disposers.push(skillsApi.register({
|
||||
|
|
@ -1123,8 +1150,32 @@ async function registerKbPackSkills(ctx, snapshot, options) {
|
|||
source: "custom"
|
||||
}));
|
||||
}
|
||||
return disposers;
|
||||
}
|
||||
async function registerKbPackSkills(ctx, snapshot, options) {
|
||||
if (!options.enabled || !kbPackSkillsEnabled(snapshot)) {
|
||||
return () => {};
|
||||
}
|
||||
const skillsApi = ctx.skills;
|
||||
if (!skillsApi || typeof skillsApi.register !== "function") {
|
||||
return () => {};
|
||||
}
|
||||
const provider = options.providerLabel ?? "netxops-kb-pack";
|
||||
const disposers = [];
|
||||
const skillsRoot = resolveKbSkillsRoot(snapshot);
|
||||
disposers.push(...await registerSkillsUnderRoot(ctx, skillsApi, skillsRoot, provider, {
|
||||
logMissing: true,
|
||||
label: "pack"
|
||||
}));
|
||||
const localRoot = resolveKbLocalSkillsRoot(snapshot);
|
||||
if (localRoot) {
|
||||
disposers.push(...await registerSkillsUnderRoot(ctx, skillsApi, localRoot, provider, {
|
||||
logMissing: false,
|
||||
label: "local"
|
||||
}));
|
||||
}
|
||||
if (disposers.length > 0) {
|
||||
ctx.logger.info("netxops: kb pack skills registered count=%s root=%s provider=%s", disposers.length, root, provider);
|
||||
ctx.logger.info("netxops: kb pack skills registered count=%s skillsRoot=%s localSkills=%s provider=%s", disposers.length, skillsRoot, localRoot ?? "(none)", provider);
|
||||
}
|
||||
return () => {
|
||||
for (const dispose of disposers)
|
||||
|
|
@ -1152,13 +1203,19 @@ function store2() {
|
|||
return current;
|
||||
}
|
||||
function getKbContext() {
|
||||
return { ...store2().snapshot, content: { ...store2().snapshot.content } };
|
||||
const snap = store2().snapshot;
|
||||
return {
|
||||
...snap,
|
||||
content: { ...snap.content },
|
||||
paths: { ...snap.paths }
|
||||
};
|
||||
}
|
||||
function publishKbContext(next) {
|
||||
const state = store2();
|
||||
state.snapshot = {
|
||||
...next,
|
||||
content: { ...next.content }
|
||||
content: { ...next.content },
|
||||
paths: { ...next.paths }
|
||||
};
|
||||
for (const listener of state.listeners)
|
||||
listener();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue