Fix Netx Ops tools missing on DSH 0.2 and public-preset publish.

Stop waiting on removed settingsScope so the host can publish a connection, harden skill frontmatter for CRLF, and auto-save capability public toggles so other presets actually get netx__* tools (0.1.50).

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-10-10 16:13:56 +08:00
parent a313dd0eae
commit d4f2459eb2
18 changed files with 567 additions and 178 deletions

View file

@ -458,11 +458,7 @@ ${declarativePatch.trimEnd()}
${DESKTOP_PATCH_END}
`;
writeFileSync(patchPath, `${patch}${managed}`, "utf8");
logger.info(
"netxops: declarative preset synced into profiles/%s/cordis.patch.yml (restart %s to load)",
profileName,
profileName === "desktop" ? "Desktop" : "dsh web"
);
logger.info("netxops: declarative preset synced into profiles/%s/cordis.patch.yml (restart %s to load)", profileName, profileName === "desktop" ? "Desktop" : "dsh web");
}
function stripManagedPresetBlocks(patch) {
let out = patch;
@ -1211,16 +1207,20 @@ function opsSkillsRoot() {
return packaged[0];
}
function parseFrontmatter(raw) {
if (!raw.startsWith("---"))
const text = raw.replace(/^\uFEFF/, "").replace(/\r\n/g, `
`).replace(/\r/g, `
`);
if (!text.startsWith("---"))
return;
const end = raw.indexOf(`
const end = text.indexOf(`
---`, 3);
if (end < 0)
return;
const yaml = raw.slice(3, end).replace(/^\r?\n/, "");
const body = raw.slice(end + 4).replace(/^\r?\n/, "");
const yaml = text.slice(3, end).replace(/^\n/, "");
const body = text.slice(end + 4).replace(/^\n/, "");
const data = {};
const lines = yaml.split(/\r?\n/);
const lines = yaml.split(`
`);
for (let i = 0;i < lines.length; i += 1) {
const line = lines[i];
const nameMatch = /^name\s*:\s*(.+)\s*$/.exec(line);
@ -1280,9 +1280,10 @@ async function loadGroupSkills(skillsRoot, groupId) {
try {
entries = await readdir(groupDir);
} catch {
return [];
return { skills: [], skipped: [{ directory: groupDir, reason: "missing group dir" }] };
}
const skills = [];
const skipped = [];
for (const entry of entries) {
const full = join3(groupDir, entry);
let isDir = false;
@ -1293,11 +1294,37 @@ async function loadGroupSkills(skillsRoot, groupId) {
}
if (!isDir)
continue;
const skill = await loadSkillBundle(full);
if (skill)
skills.push(skill);
const skillPath = join3(full, "SKILL.md");
let raw;
try {
raw = await readFile(skillPath, "utf8");
} catch {
skipped.push({ directory: full, reason: "no SKILL.md" });
continue;
}
const parsed = parseFrontmatter(raw);
if (!parsed) {
skipped.push({ directory: full, reason: "invalid frontmatter" });
continue;
}
const name = typeof parsed.data.name === "string" ? parsed.data.name.trim() : "";
const description = typeof parsed.data.description === "string" ? parsed.data.description.trim() : "";
if (!name || !description) {
skipped.push({
directory: full,
reason: !name ? "missing name" : "missing description"
});
continue;
}
skills.push({
name,
description,
content: parsed.body.trimStart(),
path: skillPath,
directory: full
});
}
return skills;
return { skills, skipped };
}
async function registerGroupSkills(ctx, groupIds, providerLabel) {
const skillsApi = ctx.skills;
@ -1307,10 +1334,17 @@ async function registerGroupSkills(ctx, groupIds, providerLabel) {
const root = opsSkillsRoot();
const disposers = [];
const enabled = new Set(groupIds);
const log = ctx.logger;
for (const groupId of CAPABILITY_GROUP_IDS) {
if (!enabled.has(groupId))
continue;
const skills = await loadGroupSkills(root, groupId);
const { skills, skipped } = await loadGroupSkills(root, groupId);
for (const miss of skipped) {
log?.warn?.("%s: skip skill dir %s (%s)", providerLabel, miss.directory, miss.reason);
}
if (skills.length === 0) {
log?.warn?.("%s: no skills loaded for group=%s from %s", providerLabel, groupId, join3(root, SKILL_DIR_BY_GROUP[groupId]));
}
for (const skill of skills) {
disposers.push(skillsApi.register({
name: skill.name,
@ -5988,8 +6022,12 @@ function apply(ctx, config = Config({})) {
if (!connection)
return;
const enabled = groupsForPlane(connection.groups, "public");
unregisterTools = registerNetxTools(toolsCtx, connection, { plane: "public" });
toolsCtx.logger.info("netxops: host public tools groups=[%s]", enabled.join(",") || "(none)");
try {
unregisterTools = registerNetxTools(toolsCtx, connection, { plane: "public" });
toolsCtx.logger.info("netxops: host public tools groups=[%s]", enabled.join(",") || "(none)");
} catch (error) {
toolsCtx.logger.error("netxops: host public tools register failed: %s", error);
}
};
const remountKbLocal = () => {
unregisterKbLocal?.();