import { createRequire } from "node:module"; var __require = /* @__PURE__ */ createRequire(import.meta.url); // src/index.ts import { readFileSync as readFileSync10 } from "node:fs"; import { dirname as dirname8, join as join14 } from "node:path"; import { fileURLToPath as fileURLToPath4 } from "node:url"; import z from "@deepseek-ai/schemastery"; import { credentialRef } from "@deepseek-ai/dsh-credentials"; import * as DshSettings from "@deepseek-ai/dsh-settings"; // src/agent-preset-install.ts import { cpSync, existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs"; import { createRequire as createRequire2 } from "node:module"; import { createHash, randomUUID } from "node:crypto"; import { homedir } from "node:os"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; var NETXOPS_PRESET_ID = "netxops"; var DESKTOP_PATCH_BEGIN = "# BEGIN dsh-netxops-preset (managed)"; var DESKTOP_PATCH_END = "# END dsh-netxops-preset (managed)"; var PATCH_PLUGIN_INDENT = " "; function packageRoot() { return join(dirname(fileURLToPath(import.meta.url)), ".."); } function resolveDshHome() { const fromEnv = process.env.DSH_HOME?.trim(); if (fromEnv !== undefined && fromEnv.length > 0) return fromEnv; return join(homedir(), ".dsh"); } function resolveActiveProfile(baseUrl = "") { const declared = process.env.DSH_PROFILE?.trim(); if (declared && /^[\w-]+$/.test(declared)) return declared; const fromBase = /[\\/]profiles[\\/]([\w-]+)[\\/]/.exec(baseUrl)?.[1]; if (fromBase) return fromBase; const flag = process.argv.indexOf("--profile"); if (flag >= 0 && /^[\w-]+$/.test(process.argv[flag + 1] ?? "")) return process.argv[flag + 1]; return process.versions.electron ? "desktop" : "web"; } function electronResourcesRoots() { const roots = []; const rp = process.resourcesPath; if (typeof rp === "string" && rp.length > 0) roots.push(rp); roots.push(join(dirname(process.execPath), "resources")); return roots; } function readableFile(path) { try { readFileSync(path, { encoding: "utf8", flag: "r" }); return true; } catch { return false; } } function resolveDesktopStandardPatchPath(profile = resolveActiveProfile()) { const dshHome = resolveDshHome(); const envOverride = process.env.NETXOPS_STANDARD_PATCH?.trim(); const candidates = []; if (envOverride) candidates.push(envOverride); for (const resources of profile === "desktop" ? electronResourcesRoots() : []) { candidates.push(join(resources, "app.asar", "dsh", "node_modules", "@deepseek-ai", "dsh-web-app", "presets", "standard.patch.yml"), join(resources, "app.asar.unpacked", "dsh", "node_modules", "@deepseek-ai", "dsh-web-app", "presets", "standard.patch.yml")); } const requireAnchors = [ join(dshHome, "profiles", profile, "package.json"), join(dshHome, "profiles", profile, "node_modules", "dsh-netxops", "package.json"), join(packageRoot(), "package.json"), join(dshHome, "profiles", "node_modules", "@deepseek-ai", "dsh-web-app", "package.json") ]; for (const anchor of requireAnchors) { if (!existsSync(anchor)) continue; try { const req = createRequire2(anchor); try { candidates.push(req.resolve("@deepseek-ai/dsh-web-app/presets/standard.patch.yml")); } catch {} const pkg = dirname(req.resolve("@deepseek-ai/dsh-web-app/package.json")); candidates.push(join(pkg, "presets", "standard.patch.yml")); } catch {} } for (const path of candidates) { if (readableFile(path)) return path; } return null; } function resolveStandardPresetDir(profile = resolveActiveProfile()) { const dshHome = resolveDshHome(); const candidates = [ join(dshHome, "profiles", profile, "node_modules", "@deepseek-ai", "dsh-agent-presets", "presets", "standard"), join(dirname(fileURLToPath(import.meta.url)), "..", "..", "DeepSeekHarness", "packages", "preset", "agent-presets", "presets", "standard"), join(dshHome, "profiles", "node_modules", "@deepseek-ai", "dsh-agent-presets", "presets", "standard") ]; for (const dir of candidates) { if (existsSync(join(dir, "agent.cordis.yml"))) return dir; } for (const nm of [ join(dshHome, "profiles", profile, "node_modules"), join(dshHome, "profiles", "node_modules") ]) { const pkgJson = join(nm, "@deepseek-ai", "dsh-agent-presets", "package.json"); if (!existsSync(pkgJson)) continue; try { const req = createRequire2(pkgJson); const root = dirname(req.resolve("@deepseek-ai/dsh-agent-presets/package.json")); const dir = join(root, "presets", "standard"); if (existsSync(join(dir, "agent.cordis.yml"))) return dir; } catch {} } try { const req = createRequire2(import.meta.url); const root = dirname(req.resolve("@deepseek-ai/dsh-agent-presets/package.json")); const dir = join(root, "presets", "standard"); if (existsSync(join(dir, "agent.cordis.yml"))) return dir; } catch {} return null; } function desktopRuntimeLikelyPresent() { if (typeof process.versions.electron === "string") return true; for (const resources of electronResourcesRoots()) { if (existsSync(join(resources, "app.asar"))) return true; } const localAppData = process.env.LOCALAPPDATA?.trim(); if (localAppData) { if (existsSync(join(localAppData, "Programs", "DeepSeek Harness", "resources", "app.asar"))) { return true; } } return false; } function hostResolveBases(profile = resolveActiveProfile()) { const dshHome = resolveDshHome(); const bases = []; for (const resources of profile === "desktop" ? electronResourcesRoots() : []) { bases.push(join(resources, "app.asar", "dsh", "package.json")); bases.push(join(resources, "app.asar", "dsh", "node_modules")); } const localAppData = process.env.LOCALAPPDATA?.trim(); if (localAppData && profile === "desktop") { const asarDsh = join(localAppData, "Programs", "DeepSeek Harness", "resources", "app.asar", "dsh"); bases.push(join(asarDsh, "package.json"), join(asarDsh, "node_modules")); } bases.push(join(dshHome, "profiles", profile, "package.json"), join(dshHome, "profiles", profile, "node_modules"), join(dshHome, "profiles", "node_modules"), join(packageRoot(), "package.json")); return [...new Set(bases.filter((path) => path.includes("app.asar") || existsSync(path)))]; } function packageInstalledAbove(name, bases) { if (name.startsWith("cordis:") || name.startsWith("node:")) return true; const pkg = name.split("/").slice(0, name.startsWith("@") ? 2 : 1).join("/"); for (const base of bases) { let dir = base.endsWith("package.json") ? dirname(base) : base; for (;; ) { if (readableFile(join(dir, "node_modules", pkg, "package.json"))) return true; if (readableFile(join(dir, pkg, "package.json"))) return true; const parent = dirname(dir); if (parent === dir) break; dir = parent; } const requireAnchor = base.endsWith("package.json") ? base : existsSync(join(base, "package.json")) ? join(base, "package.json") : null; if (requireAnchor !== null) { try { createRequire2(requireAnchor).resolve(`${pkg}/package.json`); return true; } catch {} } } return false; } function collectCompositionPackageNames(yaml) { const names = []; for (const match of yaml.matchAll(/^\s*name:\s*(.+)$/gm)) { let raw = match[1].trim(); if (raw.startsWith("'") && raw.endsWith("'") || raw.startsWith('"') && raw.endsWith('"')) { raw = raw.slice(1, -1); } if (raw.length === 0) continue; if (raw.startsWith("./") || raw.startsWith("../") || raw.startsWith("file:")) continue; names.push(raw); } return [...new Set(names)]; } function unresolvableCompositionPackages(yaml, bases) { return collectCompositionPackageNames(yaml).filter((name) => !packageInstalledAbove(name, bases)); } function remapWorkflowToPtc(cordis) { return cordis.replaceAll("- id: workflow-worker-thread", "- id: workflow-ptc").replaceAll("@deepseek-ai/dsh-workflow-worker-thread", "@deepseek-ai/dsh-workflow-ptc"); } function remapWorkflowToWorkerThread(cordis) { return cordis.replaceAll("- id: workflow-ptc", "- id: workflow-worker-thread").replaceAll("@deepseek-ai/dsh-workflow-ptc", "@deepseek-ai/dsh-workflow-worker-thread"); } function alignWorkflowEngineForHost(cordis, bases, host = "auto") { const hasPtc = packageInstalledAbove("@deepseek-ai/dsh-workflow-ptc", bases); const hasWorker = packageInstalledAbove("@deepseek-ai/dsh-workflow-worker-thread", bases); const desktop = host === "desktop" || host === "auto" && desktopRuntimeLikelyPresent(); if (hasPtc && !hasWorker) return remapWorkflowToPtc(cordis); if (hasPtc && hasWorker && desktop) return remapWorkflowToPtc(cordis); if (!hasPtc && hasWorker && desktop) { return remapWorkflowToPtc(cordis); } if (!hasPtc && !hasWorker && desktop && cordis.includes("workflow-worker-thread")) { return remapWorkflowToPtc(cordis); } if (hasWorker && !hasPtc && !desktop) return remapWorkflowToWorkerThread(cordis); if (hasWorker && hasPtc && host === "web") return remapWorkflowToWorkerThread(cordis); return cordis; } function extractPluginsBodyFromStandardPatch(patchYaml) { const text = patchYaml.replace(/\r\n/g, ` `); const marker = /^([ \t]*)plugins:\s*$/m; const match = marker.exec(text); if (match === null) { throw new Error("standard.patch.yml has no plugins: block"); } const pluginsKeyIndent = match[1].length; const start = match.index + match[0].length; const rest = text.slice(start).replace(/^\n/, ""); const lines = rest.split(` `); const body = []; for (const line of lines) { if (line.trim() === "") { body.push(line); continue; } const indent = line.match(/^[ \t]*/)?.[0].length ?? 0; if (indent <= pluginsKeyIndent) break; body.push(line); } while (body.length > 0 && body[body.length - 1].trim() === "") body.pop(); return body.join(` `); } function pluginsBodyToAgentCordis(pluginsBody) { const normalized = pluginsBody.replace(/\r\n/g, ` `); const prefix = /^([ \t]*)-\s/m.exec(normalized)?.[1]; if (prefix === undefined) throw new Error("standard preset plugins must be a YAML list"); return `${pluginsBody.replace(/\r\n/g, ` `).split(` `).map((line) => { if (line.length === 0) return ""; if (line.startsWith(prefix)) return line.slice(prefix.length); throw new Error("standard preset plugins have inconsistent indentation"); }).join(` `).trimEnd()} `; } function agentCordisToPluginsBody(agentCordis) { return agentCordis.replace(/\r\n/g, ` `).split(` `).map((line) => line.length === 0 ? "" : `${PATCH_PLUGIN_INDENT}${line}`).join(` `); } function replaceTopLevelEntry(cordis, id, replacement) { const lines = cordis.replace(/\r\n/g, ` `).split(` `); const start = lines.findIndex((line) => line === `- id: ${id}`); const block = replacement.replace(/\r\n/g, ` `).trimEnd().split(` `); if (start < 0) { return `${cordis.replace(/\r\n/g, ` `).trimEnd()} ${block.join(` `)} `; } let end = start + 1; while (end < lines.length && !lines[end].startsWith("- id:")) end += 1; while (end > start + 1 && lines[end - 1].trim() === "") end -= 1; return [...lines.slice(0, start), ...block, "", ...lines.slice(end)].join(` `).replace(/\n{3,}/g, ` `); } function ensureNetxopsToolsRow(cordis) { if (/^- id: netxops-tools$/m.test(cordis)) return cordis; const row = `- id: netxops-tools name: dsh-netxops/tools`; if (/^- id: tool-skill$/m.test(cordis)) { const lines = cordis.replace(/\r\n/g, ` `).split(` `); const start = lines.findIndex((line) => line === "- id: tool-skill"); let end = start + 1; while (end < lines.length && !lines[end].startsWith("- id:")) end += 1; while (end > start + 1 && lines[end - 1].trim() === "") end -= 1; return [...lines.slice(0, end), "", ...row.split(` `), "", ...lines.slice(end)].join(` `).replace(/\n{3,}/g, ` `); } return `${cordis.replace(/\r\n/g, ` `).trimEnd()} ${row} `; } function personaEntryFromMarkdown(md) { const body = md.replace(/\r\n/g, ` `).replace(/^#[^\n]*\n+/u, "").replace(/\n+$/u, ""); const indented = body.split(` `).map((line) => ` ${line}`).join(` `); return [ "- id: persona", " name: '@deepseek-ai/dsh-persona'", " config:", " suffix: Your working directory is {{cwd}}.", " # Body from presets/netxops/PERSONA.md (composed onto standard).", " prefix: |-", indented ].join(` `); } function readPresetMeta(metaPath) { const metaText = readFileSync(metaPath, "utf8"); return { name: (metaText.match(/^name:\s*(.+)$/m) || [])[1]?.trim() || "Netx Ops", description: (metaText.match(/^description:\s*(.+)$/m) || [])[1]?.trim() || "", order: (metaText.match(/^order:\s*(\d+)/m) || [])[1] || "50" }; } function composeNetxopsPresetArtifacts(profile = resolveActiveProfile(), standardDocument) { const overlayRoot = join(packageRoot(), "presets", NETXOPS_PRESET_ID); const personaPath = join(overlayRoot, "PERSONA.md"); const metaPath = join(overlayRoot, "preset.yml"); if (!existsSync(personaPath) || !existsSync(metaPath)) { throw new Error(`bundled preset overlay missing under ${overlayRoot}`); } const meta = readPresetMeta(metaPath); const bases = hostResolveBases(profile); const patchPath = standardDocument === undefined ? resolveDesktopStandardPatchPath(profile) : null; const usePatch = patchPath !== null && process.env.NETXOPS_PREFER_DESKTOP_PATCH !== "0" && (typeof process.versions.electron === "string" || process.env.NETXOPS_PREFER_DESKTOP_PATCH === "1" || profile === "desktop" || resolveStandardPresetDir(profile) === null); let standardSource; let standardKind; let standardDir; let agentCordis; if (standardDocument !== undefined) { if (!standardDocument.trim()) throw new Error("Host standard preset composition is empty"); standardSource = `Host ${profile}: standard preset`; standardKind = "host-registry"; standardDir = null; agentCordis = standardDocument; } else if (usePatch && patchPath !== null) { standardSource = patchPath; standardKind = "desktop-patch"; standardDir = null; agentCordis = pluginsBodyToAgentCordis(extractPluginsBodyFromStandardPatch(readFileSync(patchPath, "utf8"))); } else { const dir = resolveStandardPresetDir(profile); if (dir === null) { throw new Error("cannot find host standard preset (Desktop standard.patch.yml or @deepseek-ai/dsh-agent-presets)"); } standardSource = dir; standardKind = "directory"; standardDir = dir; agentCordis = readFileSync(join(dir, "agent.cordis.yml"), "utf8"); } agentCordis = replaceTopLevelEntry(agentCordis, "persona", personaEntryFromMarkdown(readFileSync(personaPath, "utf8"))); agentCordis = ensureNetxopsToolsRow(agentCordis); if (standardDocument === undefined) { agentCordis = alignWorkflowEngineForHost(agentCordis, bases, profile === "desktop" ? "desktop" : "web"); } if (!agentCordis.endsWith(` `)) agentCordis += ` `; const missing = (standardDocument === undefined ? unresolvableCompositionPackages(agentCordis, bases) : []).filter((name) => !name.startsWith("dsh-netxops")); const desktopAsar = profile === "desktop" && desktopRuntimeLikelyPresent(); const blocking = missing.filter((name) => !(desktopAsar && name.startsWith("@deepseek-ai/"))); if (blocking.length > 0) { throw new Error(`composed Netx Ops preset names packages the host cannot resolve: ${blocking.join(", ")} ` + `(standard-source: ${standardSource})`); } const pluginsBody = agentCordisToPluginsBody(agentCordis); const declarativePatch = `# Declarative Netx Ops preset for Desktop 0.2 (@deepseek-ai/dsh-agent-preset). # Generated from host \`standard\` + presets/netxops overlays — do not hand-edit. # Regenerate: bun run scripts/gen-preset-patch.mjs - insert: - id: preset-netxops name: '@deepseek-ai/dsh-agent-preset' config: id: netxops order: ${meta.order} name: ${JSON.stringify(meta.name)} description: ${JSON.stringify(meta.description)} plugins: ${pluginsBody} `; return { standardSource, standardKind, standardDir, ...meta, agentCordis, declarativePatch }; } function installDirectoryPreset(artifacts, logger) { const overlayRoot = join(packageRoot(), "presets", NETXOPS_PRESET_ID); const destParent = join(resolveDshHome(), ".agent-presets"); const dest = join(destParent, NETXOPS_PRESET_ID); mkdirSync(destParent, { recursive: true }); const marker = join(dest, ".dsh-netxops-managed"); if (existsSync(dest) && !existsSync(marker)) { logger.warn("netxops: preserving user-managed preset at %s", dest); return; } const existed = existsSync(dest); mkdirSync(dest, { recursive: true }); if (!existed && artifacts.standardDir !== null && existsSync(artifacts.standardDir)) { cpSync(artifacts.standardDir, dest, { recursive: true }); } let hashes = {}; if (existsSync(marker)) { try { hashes = JSON.parse(readFileSync(marker, "utf8")).files ?? {}; } catch {} } const hash = (text) => createHash("sha256").update(text).digest("hex"); const files = { "preset.yml": readFileSync(join(overlayRoot, "preset.yml"), "utf8"), "PERSONA.md": readFileSync(join(overlayRoot, "PERSONA.md"), "utf8"), "agent.cordis.yml": artifacts.agentCordis }; for (const [name, content] of Object.entries(files)) { const path = join(dest, name); if (existed && existsSync(path)) { const current = readFileSync(path, "utf8"); if (current === content) { hashes[name] = hash(content); continue; } if (hash(current) !== hashes[name]) { logger.warn("netxops: preserving edited preset file %s", path); continue; } } writeFileSync(path, content, "utf8"); hashes[name] = hash(content); } writeFileSync(marker, `${JSON.stringify({ standardSource: artifacts.standardSource, files: hashes }, null, 2)} `, "utf8"); logger.info("netxops: directory preset composed from %s → %s", artifacts.standardSource, dest); } function syncDeclarativeIntoProfilePatch(profileName, declarativePatch, logger) { if (!/^[\w-]+$/.test(profileName)) throw new Error("invalid Host profile name"); const profileDir = join(resolveDshHome(), "profiles", profileName); const patchPath = join(profileDir, "cordis.patch.yml"); if (!existsSync(profileDir)) return; const original = existsSync(patchPath) ? readFileSync(patchPath, "utf8") : `[] `; let patch = original; patch = stripManagedPresetBlocks(patch); if (/^\s*-\s+id:\s*(?:preset-netxops|"preset-netxops"|'preset-netxops')\s*(?:#.*)?$/m.test(patch)) { logger.warn("netxops: preserving user-declared preset-netxops in %s", patchPath); return; } const contentLines = patch.replace(/^\uFEFF/, "").split(/\r?\n/).filter((line) => !/^\s*(?:#.*)?$/.test(line) && !/^(?:---|\.\.\.)[ \t]*(?:#.*)?$/.test(line)); if (contentLines.length === 1 && /^\[\][ \t]*(?:#.*)?$/.test(contentLines[0])) { patch = patch.replace(/^\uFEFF/, "").replace(/^\[\][ \t]*(#.*)?$/m, (_line, comment) => comment ?? ""); } else if (contentLines.length > 0 && !/^\s*-\s/.test(contentLines[0])) { throw new Error("profile patch must be a YAML block list; preserving the existing document"); } patch = patch.replace(/^\.\.\.[ \t]*(#.*)?$/gm, (_line, comment) => comment ?? ""); if (!patch.endsWith(` `) && patch.length > 0) patch += ` `; const managed = `${DESKTOP_PATCH_BEGIN} ${declarativePatch.trimEnd()} ${DESKTOP_PATCH_END} `; const next = `${patch}${managed}`; if (next === original) return; if (existsSync(patchPath) && readFileSync(patchPath, "utf8") !== original) { logger.warn("netxops: profile patch changed during preset installation; preserving %s", patchPath); return; } const temporary = join(profileDir, `.netxops-patch-${randomUUID()}.tmp`); try { writeFileSync(temporary, next, { encoding: "utf8", flag: "wx" }); if ((existsSync(patchPath) ? readFileSync(patchPath, "utf8") : `[] `) !== original) { logger.warn("netxops: profile patch changed during preset installation; preserving %s", patchPath); return; } renameSync(temporary, patchPath); } finally { if (existsSync(temporary)) unlinkSync(temporary); } 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; for (;; ) { const start = out.indexOf(DESKTOP_PATCH_BEGIN); if (start < 0) break; const end = out.indexOf(DESKTOP_PATCH_END, start); if (end < 0) { throw new Error("unterminated managed preset block; preserving profile patch"); } let cut = end + DESKTOP_PATCH_END.length; if (out[cut] === "\r") cut += 1; if (out[cut] === ` `) cut += 1; out = `${out.slice(0, start)}${out.slice(cut)}`; } return out; } function ensureAgentPresetInstalled(logger, profile = resolveActiveProfile(), standardDocument) { let artifacts; try { artifacts = composeNetxopsPresetArtifacts(profile, standardDocument); } catch (error) { logger.warn("netxops: skip preset install: %s", error); return; } try { installDirectoryPreset(artifacts, logger); } catch (error) { logger.error("netxops: failed to install directory preset: %s", error); } try { syncDeclarativeIntoProfilePatch(profile, artifacts.declarativePatch, logger); } catch (error) { logger.error("netxops: failed to sync declarative preset into profile patch: %s", error); } } // src/netx/heal-profile-entry.ts import { existsSync as existsSync2, readFileSync as readFileSync2, statSync, writeFileSync as writeFileSync2 } from "node:fs"; var HOST_HEAL_ONCE = Symbol.for("dsh-netxops.host-bridge-heal-attempted"); var NETXOPS_HOST_CONFIG_DEFAULTS = Object.freeze({ toolsEnabled: true, groupOpsPresets: "", groupTopologyPresets: "", groupBizMonitorPresets: "", groupKbPresets: "", apiUrl: "http://127.0.0.1:8890", lang: "zh", thinkingLanguage: "auto", replyLanguage: "follow-user", nmsProvider: "zte-ume", tokenCredentialRef: "NETX_API_TOKEN", toolCallTimeoutMs: 120000, installAgentPreset: true, alarmPushEnabled: false, alarmDeliverDsh: true, alarmDeliverIm: false, imBotId: "", imTargetId: "", imTargets: "", groupOpsInPreset: true, groupOpsPublic: false, groupTopologyInPreset: false, groupTopologyPublic: false, groupBizMonitorInPreset: true, groupBizMonitorPublic: false, kbRoot: "", groupKbInPreset: true, groupKbPublic: false, disabledTools: "[]", readOnlyOnly: false }); function mergeHostConfigForHeal(existing) { const out = { ...existing }; for (const [key, value] of Object.entries(NETXOPS_HOST_CONFIG_DEFAULTS)) { if (!Object.hasOwn(out, key)) out[key] = value; } return out; } function writePatchIfUnchanged(patchPath, expectedRaw, nextRaw) { if (nextRaw === expectedRaw) return false; const existed = existsSync2(patchPath); const mtimeMs = existed ? statSync(patchPath).mtimeMs : 0; const current = existed ? readFileSync2(patchPath, "utf8") : ""; if (current !== expectedRaw) return false; if (existed && statSync(patchPath).mtimeMs !== mtimeMs) return false; writeFileSync2(patchPath, nextRaw, "utf8"); return true; } // src/netx/http.ts function normalizeApiUrl(raw) { const trimmed = typeof raw === "string" ? raw.trim() : ""; if (!trimmed) return { ok: false, error: "empty" }; let parsed; try { parsed = new URL(trimmed); } catch { return { ok: false, error: "invalid_url" }; } if (parsed.protocol !== "http:" && parsed.protocol !== "https:") { return { ok: false, error: "unsupported_scheme" }; } if (parsed.username || parsed.password) { return { ok: false, error: "credentials_in_url" }; } const path = parsed.pathname === "/" ? "" : parsed.pathname.replace(/\/$/, ""); const search = parsed.search && parsed.search !== "?" ? parsed.search : ""; return { ok: true, url: `${parsed.origin}${path}${search}` }; } // src/netx/alarm-push-status.ts var STORE_KEY = Symbol.for("dsh-netxops.alarm-push-status"); function emptyStatus(partial = {}) { return { phase: "disabled", enabled: false, wsUrl: "", detail: "", updatedAt: Date.now(), lastConnectedAt: null, lastError: null, ...partial }; } function store() { const root = globalThis; let current = root[STORE_KEY]; if (current === undefined) { current = { status: emptyStatus(), listeners: new Set }; root[STORE_KEY] = current; } return current; } function getAlarmPushStatus() { return { ...store().status }; } function publishAlarmPushStatus(next) { const state = store(); state.status = { ...state.status, ...next, updatedAt: Date.now() }; for (const listener of state.listeners) listener(); } function resetAlarmPushStatus() { publishAlarmPushStatus(emptyStatus({ phase: "disabled", enabled: false })); } // src/netx/i18n.ts var ZH = { "alarm.sessionTitle": "Netx 关键告警", "alarm.defaultLabel": "关键告警", "alarm.action.inserted": "告警产生", "alarm.action.updated": "告警更新", "alarm.action.deleted": "告警清除", "alarm.action.fallback": "告警", "alarm.field.device": "设备", "alarm.field.object": "对象", "alarm.field.severity": "级别", "alarm.field.cause": "原因", "alarm.field.time": "时间", "alarm.analyzeHint": "请分析这条关键告警并给出下一步运维建议。" }; var EN = { "alarm.sessionTitle": "Netx key alarm", "alarm.defaultLabel": "Key alarm", "alarm.action.inserted": "Alarm Raised", "alarm.action.updated": "Alarm Updated", "alarm.action.deleted": "Alarm Cleared", "alarm.action.fallback": "Alarm", "alarm.field.device": "Device", "alarm.field.object": "Object", "alarm.field.severity": "Severity", "alarm.field.cause": "Cause", "alarm.field.time": "Time", "alarm.analyzeHint": "Please analyze this key alarm and suggest next ops steps." }; var TABLES = { zh: ZH, en: EN }; function normalizeNetxLocale(lang) { const normalized = String(lang ?? "").trim().toLowerCase(); if (normalized === "english" || /^en(?:[-_].*)?$/u.test(normalized)) return "en"; return "zh"; } function tHost(key, lang = "zh", vars) { const locale = normalizeNetxLocale(lang); const template = TABLES[locale][key] ?? TABLES.zh[key] ?? key; if (vars === undefined) return template; return template.replace(/\{(\w+)\}/g, (match, name) => Object.prototype.hasOwnProperty.call(vars, name) ? String(vars[name]) : match); } function alarmActionLabel(action, lang) { const normalized = String(action ?? "").trim().toLowerCase(); if (normalized === "inserted") return tHost("alarm.action.inserted", lang); if (normalized === "updated") return tHost("alarm.action.updated", lang); if (normalized === "deleted") return tHost("alarm.action.deleted", lang); return normalized || tHost("alarm.action.fallback", lang); } // src/netx/alarm-push.ts function alarmSubscribeUrl(apiUrl) { const trimmed = apiUrl.trim().replace(/\/$/, ""); if (!trimmed) return ""; let url; try { url = new URL(trimmed); } catch { return ""; } if (url.protocol !== "http:" && url.protocol !== "https:") return ""; if (url.username || url.password) return ""; url.protocol = url.protocol === "https:" ? "wss:" : "ws:"; url.pathname = "/v1/integrations/dsh-alarm/ws"; url.search = ""; url.hash = ""; url.username = ""; url.password = ""; return url.toString(); } function formatAlarmPrompt(payload, lang = "zh") { const action = String(payload.action ?? "").trim().toLowerCase(); const ne = payload.ne && typeof payload.ne === "object" ? payload.ne : {}; const host = String(ne.host_name ?? "").trim(); const ip = String(ne.ip_address ?? "").trim(); const neName = String(ne.ne_name ?? ne.user_label ?? "").trim(); let device = host || neName || String(payload.ne_id ?? "").trim() || "-"; if (ip) device = device === "-" ? ip : `${device} (${ip})`; const label = String(payload.rule_label ?? payload.native_probable_cause ?? tHost("alarm.defaultLabel", lang)).trim(); return [ `[NMS ${alarmActionLabel(action, lang)}] ${label}`, `${tHost("alarm.field.device", lang)}: ${device}`, `${tHost("alarm.field.object", lang)}: ${String(payload.object_name ?? "-").trim()}`, `${tHost("alarm.field.severity", lang)}: ${String(payload.perceived_severity ?? "-").trim()}`, `${tHost("alarm.field.cause", lang)}: ${String(payload.native_probable_cause ?? "-").trim()}`, `${tHost("alarm.field.time", lang)}: ${String(payload.time_created ?? "-").trim()}`, `notificationId: ${String(payload.notification_id ?? "-").trim()}`, `alarm_key: ${String(payload.alarm_key ?? "-").trim()}`, "", tHost("alarm.analyzeHint", lang) ].join(` `); } function setPhase(phase, wsUrl, extra = {}) { publishAlarmPushStatus({ phase, enabled: true, wsUrl, detail: extra.detail ?? "", lastError: extra.lastError === undefined ? null : extra.lastError, ...extra.lastConnectedAt !== undefined ? { lastConnectedAt: extra.lastConnectedAt } : {} }); } function startAlarmPushClient(options) { const log = options.logger ?? console; const wsUrl = alarmSubscribeUrl(options.apiUrl); const token = options.token.trim(); if (!wsUrl || !token) { log.warn?.("netxops alarm-push: missing apiUrl or token — not connecting"); setPhase("error", wsUrl, { detail: "missing_url_or_token", lastError: "missing apiUrl or token" }); return () => {}; } const WS = options.WebSocketImpl ?? globalThis.WebSocket; if (typeof WS !== "function") { log.error?.("netxops alarm-push: WebSocket is unavailable in this runtime"); setPhase("error", wsUrl, { detail: "websocket_unavailable", lastError: "WebSocket unavailable" }); return () => {}; } let closed = false; let haltReconnect = false; let subscribed = false; let socket = null; let reconnectTimer; let attempt = 0; const baseDelay = Math.max(500, options.reconnectMs ?? 2000); const clearReconnect = () => { if (reconnectTimer !== undefined) { clearTimeout(reconnectTimer); reconnectTimer = undefined; } }; const scheduleReconnect = (reason) => { if (closed || haltReconnect) return; clearReconnect(); const delay = Math.min(60000, baseDelay * 2 ** Math.min(attempt, 5)); attempt += 1; setPhase("reconnecting", wsUrl, { detail: `retry_in_${delay}ms`, lastError: reason }); reconnectTimer = setTimeout(() => { connect(); }, delay); }; const connect = () => { if (closed || haltReconnect) return; clearReconnect(); subscribed = false; setPhase(attempt > 0 ? "reconnecting" : "connecting", wsUrl, { detail: "dialing" }); try { socket = new WS(wsUrl); } catch (error) { const message = error instanceof Error ? error.message : String(error); log.warn?.("netxops alarm-push: connect failed:", error); scheduleReconnect(message); return; } socket.addEventListener("open", () => { setPhase("authenticating", wsUrl, { detail: "auth" }); let clientLabel = "netxops"; try { const env = typeof process !== "undefined" ? process.env : undefined; const hostname = env?.HOSTNAME || env?.COMPUTERNAME; if (typeof hostname === "string" && hostname.trim()) { clientLabel = `netxops@${hostname.trim()}`; } } catch {} socket?.send(JSON.stringify({ type: "auth", token, client: clientLabel })); }); socket.addEventListener("message", (event) => { let msg; try { msg = JSON.parse(String(event.data)); } catch { return; } const type = String(msg.type ?? "").toLowerCase(); if (type === "auth-ok") { attempt = 0; subscribed = true; const now = Date.now(); setPhase("connected", wsUrl, { detail: String(msg.user ?? "ok"), lastError: null, lastConnectedAt: now }); log.info?.("netxops alarm-push: subscribed to %s", wsUrl); return; } if (type === "auth-fail") { const err = String(msg.error ?? "auth_failed"); log.error?.("netxops alarm-push: auth failed (%s) — not reconnecting until settings/token change", err); subscribed = false; haltReconnect = true; clearReconnect(); setPhase("auth_failed", wsUrl, { detail: err, lastError: err }); try { socket?.close(); } catch {} return; } if (type === "pong") return; if (type === "event" && String(msg.event ?? "") === "netx.alarm") { if (!subscribed) { log.warn?.("netxops alarm-push: ignoring alarm before auth-ok"); return; } const payload = msg.payload && typeof msg.payload === "object" ? msg.payload : {}; Promise.resolve(options.onAlarm(payload)).catch((error) => { log.warn?.("netxops alarm-push: handler failed:", error); }); } }); socket.addEventListener("close", () => { socket = null; subscribed = false; if (!closed && !haltReconnect) scheduleReconnect("socket_closed"); }); socket.addEventListener("error", () => {}); }; const pingTimer = setInterval(() => { if (socket && socket.readyState === WS.OPEN) { try { socket.send(JSON.stringify({ type: "ping", ts: new Date().toISOString() })); } catch {} } }, 25000); connect(); return () => { closed = true; clearReconnect(); clearInterval(pingTimer); try { socket?.close(); } catch {} socket = null; }; } // src/netx/im-targets.ts function imTargetKey(botId, targetId) { return `${botId}::${targetId}`; } function normalizeImTarget(input) { if (input === null || typeof input !== "object" || Array.isArray(input)) return null; const row = input; const botId = typeof row.botId === "string" ? row.botId.trim() : ""; const targetId = typeof row.targetId === "string" ? row.targetId.trim() : ""; if (!botId || !targetId) return null; return { botId, targetId }; } function parseImTargetsJson(text) { const trimmed = text.trim(); if (!trimmed) return []; let parsed; try { parsed = JSON.parse(trimmed); } catch { return []; } if (!Array.isArray(parsed)) return []; const out = []; const seen = new Set; for (const entry of parsed) { const target = normalizeImTarget(entry); if (!target) continue; const key = imTargetKey(target.botId, target.targetId); if (seen.has(key)) continue; seen.add(key); out.push(target); } return out; } function resolveImTargets(settings) { const fromList = parseImTargetsJson(typeof settings.imTargets === "string" ? settings.imTargets : ""); if (fromList.length > 0) return fromList; const legacy = normalizeImTarget({ botId: settings.imBotId, targetId: settings.imTargetId }); return legacy ? [legacy] : []; } // src/netx/alarm-im.ts function resolveTargets(options) { if (options.targets && options.targets.length > 0) { const out = []; const seen = new Set; for (const entry of options.targets) { const target = normalizeImTarget(entry); if (!target) continue; const key = `${target.botId}::${target.targetId}`; if (seen.has(key)) continue; seen.add(key); out.push(target); } return out; } return resolveImTargets({ imTargets: options.imTargets, imBotId: options.botId, imTargetId: options.targetId }); } async function deliverAlarmToIm(ctx, payload, options) { if (!options.enabled) return; const targets = resolveTargets(options); if (targets.length === 0) { ctx.logger.warn("netxops alarm-im: enabled but no delivery targets — skip IM delivery (pick one or more targets under Netx Ops → IM)"); return; } const im = (typeof ctx.get === "function" ? ctx.get("dshIm") : undefined) ?? ctx.dshIm; if (!im || typeof im.send !== "function") { ctx.logger.warn("netxops alarm-im: ctx.dshIm unavailable — install/enable dsh-im-ops to deliver alarms to WhatsApp/IM"); return; } const text = formatAlarmPrompt(payload, options.lang); const results = await Promise.allSettled(targets.map((target) => im.send(target.botId, target.targetId, text))); results.forEach((result, index) => { const target = targets[index]; if (result.status === "fulfilled") { ctx.logger.info("netxops alarm-im: sent to botId=%s targetId=%s", target.botId, target.targetId); return; } ctx.logger.warn("netxops alarm-im: send failed botId=%s targetId=%s: %s", target.botId, target.targetId, result.reason); }); } // src/netx/alarm-session.ts import { homedir as homedir2 } from "node:os"; import { join as join2 } from "node:path"; var PRESET_ID = "netxops"; var PERMISSION_PRESET = "default"; var sticky = null; var deliveryChain = Promise.resolve(); function resolveWorkspacePath() { const fromEnv = process.env.DSH_HOME?.trim(); const home = fromEnv && fromEnv.length > 0 ? fromEnv : join2(homedir2(), ".dsh"); return join2(home, "workspaces", "netxops-alarms"); } async function disposeSticky(handle) { if (!handle) return; try { await handle.dispose?.(); } catch {} } async function deliverAlarmToSession(ctx, payload, lang = "zh") { const run = async () => { const prompt = formatAlarmPrompt(payload, lang); const agents = ctx.agents; if (!agents || typeof agents.create !== "function") { ctx.logger.warn("netxops alarm-push: ctx.agents unavailable — enable a profile that mounts agents to receive alarms in a DSH session"); return; } if (sticky?.agent && typeof sticky.agent.followup === "function") { try { await followup(ctx, sticky.agent, prompt); return; } catch (error) { ctx.logger.warn("netxops alarm-push: sticky followup failed, recreating session: %s", error); const previous = sticky; sticky = null; await disposeSticky(previous); } } await createStickySession(ctx, prompt, lang); }; const next = deliveryChain.then(run, run); deliveryChain = next.then(() => { return; }, () => { return; }); await next; } async function followup(ctx, agent, prompt) { let createUserMessage; try { const mod = await import("@deepseek-ai/dsh-llm"); createUserMessage = mod.createUserMessage; if (typeof createUserMessage !== "function") throw new Error("createUserMessage missing"); const summary = typeof mod.boundContextSummary === "function" ? mod.boundContextSummary("netx key alarm") : "netx key alarm"; await Promise.resolve(agent.followup(createUserMessage({ content: [{ type: "text", text: prompt }], source: { kind: "webhook", provider: "netx", source: "dsh-alarm-hub", form: "notice", summary } }))); } catch (error) { const anyAgent = agent; if (typeof anyAgent.prompt === "function") { await anyAgent.prompt(prompt); return; } ctx.logger.warn("netxops alarm-push: cannot build user message (%s)", error); throw error; } } async function createStickySession(ctx, prompt, lang = "zh") { const c = ctx; const agents = c.agents; const agentPresets = c.agentPresets; const workspaceRegistry = c.workspaceRegistry; const permissionPresets = c.permissionPresets; const sessionTitle = c.sessionTitle; const agentDefaultModel = c.agentDefaultModel; if (!agentPresets || typeof agentPresets.resolve !== "function" || !workspaceRegistry || typeof workspaceRegistry.create !== "function") { ctx.logger.warn("netxops alarm-push: agentPresets/workspaceRegistry unavailable — cannot create a DSH session"); return; } if (permissionPresets && typeof permissionPresets.resolve === "function") { try { permissionPresets.resolve(PERMISSION_PRESET); } catch { ctx.logger.warn("netxops alarm-push: permission preset %s missing", PERMISSION_PRESET); } } const preset = await agentPresets.resolve(PRESET_ID); if (typeof agentPresets.standingKeyFor === "function") { await agentPresets.standingKeyFor(preset.id); } const selected = typeof agentDefaultModel?.currentSelection === "function" ? agentDefaultModel.currentSelection() : { provider: "deepseek", model: "deepseek-chat" }; const workspacePath = resolveWorkspacePath(); const workspace = await workspaceRegistry.create(workspacePath); const sessionId = `netxops-alarm-${Date.now().toString(36)}`; const handle = await agents.create({ sessionId, meta: { cwd: workspace.path, agentPreset: preset.id }, agentOptions: { provider: selected.provider, model: selected.model }, setup: async (agentCtx) => { if (typeof agentPresets.mount === "function") { await agentPresets.mount(agentCtx, preset.id); } } }); try { if (typeof workspace.attachSession === "function") { await workspace.attachSession(sessionId); } if (permissionPresets && typeof permissionPresets.set === "function") { permissionPresets.set(handle.agent.session, PERMISSION_PRESET); } if (sessionTitle && typeof sessionTitle.rename === "function") { sessionTitle.rename(handle.agent.session, tHost("alarm.sessionTitle", lang)); } await followup(ctx, handle.agent, prompt); sticky = { sessionId, agent: handle.agent, dispose: typeof handle.dispose === "function" ? () => handle.dispose() : undefined }; ctx.logger.info("netxops alarm-push: opened sticky session %s", sessionId); } catch (error) { try { await handle.dispose?.(); } catch {} throw error; } } function resetAlarmSession() { const previous = sticky; sticky = null; disposeSticky(previous); } // src/netx/alarm-dispatch.ts async function dispatchAlarmToSinks(ctx, payload, options, sinks = {}) { const toSession = sinks.toSession ?? deliverAlarmToSession; const toIm = sinks.toIm ?? deliverAlarmToIm; const jobs = []; if (options.deliverDsh) { jobs.push(toSession(ctx, payload, options.lang)); } const imOptions = { enabled: options.deliverIm, targets: options.imTargets, lang: options.lang }; jobs.push(toIm(ctx, payload, imOptions)); const results = await Promise.allSettled(jobs); for (const result of results) { if (result.status === "rejected") { ctx.logger.warn("netxops alarm-push: sink failed: %s", result.reason); } } return results; } // src/netx/config-values.ts var volatileWrite = Symbol.for("cosmokit.volatile.write"); function readConfigValue(value) { if (value !== null && typeof value === "object" && volatileWrite in value && "get" in value && typeof value.get === "function") { return value.get(); } return value; } function readConfigSnapshot(input) { return Object.fromEntries(Object.entries(input).map(([key, value]) => [key, readConfigValue(value)])); } // src/netx/capability-groups.ts var DEFAULT_CAPABILITY_GROUPS = Object.freeze({ ops: Object.freeze({ inPreset: true, public: false }), topology: Object.freeze({ inPreset: false, public: false }), bizMonitor: Object.freeze({ inPreset: true, public: false }) }); var SKILL_DIR_BY_GROUP = Object.freeze({ ops: "ops", topology: "topology", bizMonitor: "biz-monitor" }); var CAPABILITY_GROUP_IDS = Object.freeze([ "ops", "topology", "bizMonitor" ]); function capabilityGroupsFromSettings(fields) { const src = readConfigSnapshot(fields ?? {}); let opsInPreset = true; if (typeof src.groupOpsInPreset === "boolean") { opsInPreset = src.groupOpsInPreset; } else { const legacy = [ src.groupNmsInPreset, src.groupCommonInPreset, src.groupManagedNeInPreset ].filter((v) => typeof v === "boolean"); if (legacy.length > 0) { opsInPreset = legacy.some((v) => v === true); } } let bizMonitorInPreset = true; if (typeof src.groupBizMonitorInPreset === "boolean") { bizMonitorInPreset = src.groupBizMonitorInPreset; } return { ops: { inPreset: opsInPreset, public: (() => { if (typeof src.groupOpsPublic === "boolean") return src.groupOpsPublic; const legacy = [ src.groupNmsPublic, src.groupCommonPublic, src.groupManagedNePublic ].filter((v) => typeof v === "boolean"); if (legacy.length === 0) return false; return legacy.some((v) => v === true); })(), ...src.groupOpsPresets ? { presets: parsePresetIds(src.groupOpsPresets) } : {} }, topology: { inPreset: typeof src.groupTopologyInPreset === "boolean" ? src.groupTopologyInPreset : src.groupTopologyLayoutInPreset === true, public: typeof src.groupTopologyPublic === "boolean" ? src.groupTopologyPublic : src.groupTopologyLayoutPublic === true, ...src.groupTopologyPresets ? { presets: parsePresetIds(src.groupTopologyPresets) } : {} }, bizMonitor: { inPreset: bizMonitorInPreset, public: src.groupBizMonitorPublic === true, ...src.groupBizMonitorPresets ? { presets: parsePresetIds(src.groupBizMonitorPresets) } : {} } }; } function kbEnabledForPlane(flags, plane, presetId = "netxops") { if (flags) flags = readConfigSnapshot(flags); if (flags?.toolsEnabled === false) return false; if (!flags) return plane === "preset"; if (plane === "preset") { return !flags.groupKbPublic && (presetId === "netxops" ? flags.groupKbInPreset !== false : parsePresetIds(flags.groupKbPresets ?? "").includes(presetId)); } return flags.groupKbPublic === true; } function groupsForPlane(groups, plane, only) { const policy = groups ?? DEFAULT_CAPABILITY_GROUPS; const enabled = CAPABILITY_GROUP_IDS.filter((id) => { if (plane === "preset") return policy[id].inPreset; return policy[id].public; }); if (!only || only.length === 0) return enabled; const allow = new Set(only); return enabled.filter((id) => allow.has(id)); } function parsePresetIds(value) { const current = readConfigValue(value); if (typeof current !== "string") return []; return [...new Set(current.split(/[\s,,]+/).filter(Boolean))]; } function groupsForPreset(connection, presetId, only) { if (connection.toolsEnabled === false) return []; const policy = connection.groups ?? DEFAULT_CAPABILITY_GROUPS; return CAPABILITY_GROUP_IDS.filter((id) => (!only?.length || only.includes(id)) && !policy[id].public && (presetId === "netxops" ? policy[id].inPreset : policy[id].presets?.includes(presetId))); } // src/netx/group-skills.ts import { readdir, readFile, stat } from "node:fs/promises"; import { join as join4 } from "node:path"; // 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 publishNetxConnection(next) { const state = store2(); if (JSON.stringify(state.connection) === JSON.stringify(next)) return; state.connection = next; for (const listener of [...state.listeners]) { try { listener(); } catch (error) { console.error("netxops: connection subscriber failed", error); } } } function getNetxConnection() { return store2().connection; } function provisionalNetxConnection() { const envUrl = process.env.NETX_API_URL?.trim() ?? ""; const normalized = envUrl ? normalizeApiUrl(envUrl) : normalizeApiUrl("http://127.0.0.1:8890"); return { toolsEnabled: false, apiUrl: normalized.ok ? normalized.url : "http://127.0.0.1:8890", token: process.env.NETX_API_TOKEN?.trim() ?? "", lang: typeof process.env.NETX_LANG === "string" && process.env.NETX_LANG.trim() ? process.env.NETX_LANG.trim() : "zh", toolCallTimeoutMs: 120000, groups: DEFAULT_CAPABILITY_GROUPS, disabledTools: [], readOnlyOnly: false, groupKbInPreset: true, groupKbPublic: false }; } function requireNetxConnection() { return getNetxConnection() ?? provisionalNetxConnection(); } function watchNetxConnection(listener) { const state = store2(); state.listeners.add(listener); return () => { state.listeners.delete(listener); }; } // src/netx/skills-cache.ts import { createHash as createHash2 } from "node:crypto"; import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync3, rmSync, statSync as statSync2, writeFileSync as writeFileSync3 } from "node:fs"; import { homedir as homedir3 } from "node:os"; import { dirname as dirname2, join as join3 } from "node:path"; var STALE_CACHE_MS = 30 * 24 * 60 * 60 * 1000; function dshHome() { const fromEnv = process.env.DSH_HOME?.trim(); return fromEnv || join3(homedir3(), ".dsh"); } function normalizeSkillsApiUrl(apiUrl) { const normalized = normalizeApiUrl(apiUrl); return normalized.ok ? normalized.url : apiUrl.trim().replace(/\/$/, ""); } function skillsCacheKey(apiUrl) { const base = normalizeSkillsApiUrl(apiUrl); return createHash2("sha256").update(base).digest("hex").slice(0, 16); } function cacheParent() { return join3(dshHome(), "netxops-mcp-skills"); } function skillsCacheDir(apiUrl) { return join3(cacheParent(), skillsCacheKey(apiUrl)); } function tokenFingerprint(token) { const raw = token.trim(); return raw ? createHash2("sha256").update(raw).digest("hex").slice(0, 16) : ""; } function readMeta(root) { try { const meta = JSON.parse(readFileSync3(join3(root, ".meta.json"), "utf8")); return meta && typeof meta === "object" && !Array.isArray(meta) ? meta : null; } catch { return null; } } function usableSkillsCacheDir(apiUrl, token) { if (!apiUrl.trim()) return ""; const root = skillsCacheDir(apiUrl); const pulledWith = readMeta(root)?.tokenFp; if (pulledWith && pulledWith !== tokenFingerprint(token)) return ""; return root; } function cachedUnavailableTools(root) { const raw = readMeta(root)?.unavailableTools; const out = {}; if (!raw || typeof raw !== "object") return out; for (const group of CAPABILITY_GROUP_IDS) { const names = raw[group]; if (Array.isArray(names)) out[group] = names.filter((n) => typeof n === "string"); } return out; } function pruneStaleCaches(keep) { const parent = cacheParent(); let entries; try { entries = readdirSync(parent); } catch { return; } const now = Date.now(); for (const name of entries) { const dir = join3(parent, name); if (dir === keep) continue; try { const listedAt = Date.parse(readMeta(dir)?.listedAt ?? ""); const age = now - (Number.isFinite(listedAt) ? listedAt : statSync2(dir).mtimeMs); if (age > STALE_CACHE_MS) rmSync(dir, { recursive: true, force: true }); } catch {} } } function isSafeSegment(value) { return Boolean(value) && !value.includes("..") && !value.includes("/") && !value.includes("\\") && !value.includes("\x00"); } function isSafeRelPath(relPath) { if (!relPath || relPath.startsWith("/") || relPath.includes("\x00")) return false; const parts = relPath.replace(/\\/g, "/").split("/"); return parts.every((part) => part.length > 0 && part !== "." && part !== ".."); } function replaceSkillsCache(apiUrl, files, options = {}) { const root = skillsCacheDir(apiUrl); rmSync(root, { recursive: true, force: true }); mkdirSync2(root, { recursive: true }); let written = 0; const unavailableTools = {}; for (const file of files) { if (file.unavailableTools?.length && SKILL_DIR_BY_GROUP[file.group]) { const merged = new Set([...unavailableTools[file.group] ?? [], ...file.unavailableTools]); unavailableTools[file.group] = [...merged].sort(); } const groupDir = SKILL_DIR_BY_GROUP[file.group]; if (!groupDir || !isSafeSegment(file.skill) || !isSafeRelPath(file.relPath)) continue; const rel = file.relPath.replace(/\\/g, "/"); const dest = join3(root, groupDir, file.skill, ...rel.split("/")); mkdirSync2(dirname2(dest), { recursive: true }); const text = file.text.replace(/\r\n/g, ` `).replace(/\r/g, ` `); writeFileSync3(dest, text, "utf8"); written += 1; } const normalized = normalizeSkillsApiUrl(apiUrl); const meta = { apiUrl: normalized, mcpUrl: normalized ? `${normalized}/mcp` : "", listedAt: new Date().toISOString(), fileCount: written, ...options.token !== undefined ? { tokenFp: tokenFingerprint(options.token) } : {}, unavailableTools }; writeFileSync3(join3(root, ".meta.json"), `${JSON.stringify(meta, null, 2)} `, "utf8"); summaryMemo.delete(root); pruneStaleCaches(root); return root; } function countFiles(dir) { let n = 0; for (const entry of readdirSync(dir)) { const full = join3(dir, entry); n += statSync2(full).isDirectory() ? countFiles(full) : 1; } return n; } var summaryMemo = new Map; function listCachedSkills(apiUrl, token = "") { const root = usableSkillsCacheDir(apiUrl, token); if (!root || !existsSync3(root)) return { listedAt: null, skills: [] }; const metaListedAt = readMeta(root)?.listedAt; const listedAt = typeof metaListedAt === "string" ? metaListedAt : null; const memo = summaryMemo.get(root); if (memo && memo.listedAt === listedAt) return memo; const skills = []; for (const group of CAPABILITY_GROUP_IDS) { const groupDir = join3(root, SKILL_DIR_BY_GROUP[group]); if (!existsSync3(groupDir)) continue; try { for (const name of readdirSync(groupDir).sort()) { const dir = join3(groupDir, name); if (!statSync2(dir).isDirectory() || !existsSync3(join3(dir, "SKILL.md"))) continue; skills.push({ group, name, files: countFiles(dir) }); } } catch {} } const listing = { listedAt, skills }; summaryMemo.set(root, listing); return listing; } // src/netx/mcp-types.ts var NETX_TOOL_PREFIX = "netx__"; function publicToolName(serverName) { return `${NETX_TOOL_PREFIX}${serverName}`; } // src/netx/group-skills.ts function parseFrontmatter(raw) { const text = raw.replace(/^\uFEFF/, "").replace(/\r\n/g, ` `).replace(/\r/g, ` `); if (!text.startsWith("---")) return; const end = text.indexOf(` ---`, 3); if (end < 0) return; const yaml = text.slice(3, end).replace(/^\n/, ""); const body = text.slice(end + 4).replace(/^\n/, ""); const data = {}; 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); if (nameMatch) { data.name = stripQuotes(nameMatch[1].trim()); continue; } const descMatch = /^description\s*:\s*(.*)$/.exec(line); if (!descMatch) continue; const head = descMatch[1].trim(); if (head === ">" || head === ">-" || head === "|" || head === "|-") { const parts = []; while (i + 1 < lines.length && /^[ \t]+/.test(lines[i + 1])) { i += 1; parts.push(lines[i].trim()); } data.description = parts.filter(Boolean).join(" "); continue; } data.description = stripQuotes(head); } return { data, body }; } function stripQuotes(value) { if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) { return value.slice(1, -1); } return value; } async function loadSkillBundle(dir) { const skillPath = join4(dir, "SKILL.md"); let raw; try { raw = await readFile(skillPath, "utf8"); } catch { return null; } const parsed = parseFrontmatter(raw); if (!parsed) return null; 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) return null; return { name, description, content: parsed.body.trimStart(), path: skillPath, directory: dir }; } async function loadGroupSkills(skillsRoot, groupId) { const groupDir = join4(skillsRoot, SKILL_DIR_BY_GROUP[groupId]); let entries; try { entries = await readdir(groupDir); } catch { return { skills: [], skipped: [{ directory: groupDir, reason: "missing group dir" }] }; } const skills = []; const skipped = []; for (const entry of entries) { const full = join4(groupDir, entry); let isDir = false; try { isDir = (await stat(full)).isDirectory(); } catch { continue; } if (!isDir) continue; const skillPath = join4(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, skipped }; } function disabledFootnote(names, lang) { if (names.length === 0) return ""; const list = names.join(", "); const en = (lang ?? "").trim().toLowerCase().startsWith("en"); return en ? ` ## Unavailable tools The following tools are disabled or not granted to this token: ${list}. Do not call them; skip steps that need them. ` : ` ## 不可用工具 以下工具已禁用或当前 token 无权使用:${list},不要调用,涉及它们的步骤请跳过。 `; } function resolveSkillsRoot(options) { if (typeof options.skillsRoot === "string" && options.skillsRoot.trim()) { return options.skillsRoot.trim(); } const connection = getNetxConnection(); if (!connection?.apiUrl?.trim()) return ""; return usableSkillsCacheDir(connection.apiUrl, connection.token ?? ""); } async function registerGroupSkills(ctx, groupIds, providerLabel, signalOrOptions) { const options = signalOrOptions instanceof AbortSignal || signalOrOptions === undefined ? { signal: signalOrOptions } : signalOrOptions; const signal = options.signal; const skillsApi = ctx.skills; if (!skillsApi || typeof skillsApi.register !== "function") { return () => {}; } const root = resolveSkillsRoot(options); const disposers = []; const enabled = new Set(groupIds); const log = ctx.logger; if (!root) { if (enabled.size > 0) { log?.warn?.("%s: no MCP skills cache (missing apiUrl)", providerLabel); } return () => {}; } const unavailable = cachedUnavailableTools(root); try { for (const groupId of CAPABILITY_GROUP_IDS) { if (!enabled.has(groupId)) continue; const { skills, skipped } = await loadGroupSkills(root, groupId); if (signal?.aborted) break; 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, join4(root, SKILL_DIR_BY_GROUP[groupId])); } const offNames = new Set([ ...options.disabledByGroup?.[groupId] ?? [], ...(unavailable[groupId] ?? []).map(publicToolName) ]); const footnote = disabledFootnote([...offNames].sort(), options.lang); for (const skill of skills) { const base = groupId === "topology" ? `${skill.content} ## DeepSeek Harness REST capabilities Use only tools present in this session. Dual-unit/orbit/polish recipes require server-side topology tools. Use only tools present in this session. ` : skill.content; disposers.push(skillsApi.register({ name: skill.name, description: skill.description, content: `${base}${footnote}`, path: skill.path, resourceBase: { kind: "directory", path: skill.directory }, provider: providerLabel, source: "custom" })); } } if (signal?.aborted) { for (const dispose of disposers.splice(0)) dispose(); } } catch (error) { for (const dispose of disposers.splice(0)) dispose(); throw error; } return () => { for (const dispose of disposers) dispose(); }; } // src/netx/kb-context-skill.ts import { readdirSync as readdirSync3, readFileSync as readFileSync5, statSync as statSync5 } from "node:fs"; import { join as join7 } from "node:path"; // src/netx/kb-manifest.ts import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync as statSync3 } from "node:fs"; import { basename, dirname as dirname3, join as join5, resolve } from "node:path"; var EMPTY_CONTENT = { hasRegions: false, hasTheory: false, hasPacket: false, hasCommon: false, hasSkills: false }; var LEGACY_CONTENT_KEY = { regions: "hasRegions", theory: "hasTheory", packet: "hasPacket", skills: "hasSkills", common: "hasCommon" }; function unconfiguredKbSnapshot() { return { status: "unconfigured", realRoot: "", operatorName: "", country: "", version: "", content: { ...EMPTY_CONTENT }, paths: {}, errorMessage: "" }; } function errorSnapshot(message) { return { status: "error", realRoot: "", operatorName: "", country: "", version: "", content: { ...EMPTY_CONTENT }, paths: {}, errorMessage: message }; } function findManifest(kbRoot, maxDepth = 3) { const root = resolve(kbRoot.trim()); if (!kbRoot.trim()) { return { paths: [], error: "kbRoot is empty" }; } let rootStat; try { rootStat = statSync3(root); } catch { return { paths: [], error: `kbRoot not found: ${root}` }; } if (!rootStat.isDirectory()) { return { paths: [], error: `kbRoot is not a directory: ${root}` }; } const direct = join5(root, "MANIFEST.json"); if (existsSync4(direct)) { try { if (statSync3(direct).isFile()) return { paths: [direct] }; } catch {} } const found = []; const walk = (dir, depth) => { if (depth > maxDepth) return; const candidate = join5(dir, "MANIFEST.json"); if (existsSync4(candidate)) { try { if (statSync3(candidate).isFile()) found.push(candidate); } catch {} } if (depth === maxDepth) return; let entries; try { entries = readdirSync2(dir); } catch { return; } for (const name of entries) { if (name === "node_modules" || name === ".git") continue; const full = join5(dir, name); let st; try { st = statSync3(full); } catch { continue; } if (st.isDirectory()) walk(full, depth + 1); } }; let topEntries; try { topEntries = readdirSync2(root); } catch { return { paths: [], error: `cannot read kbRoot: ${root}` }; } for (const name of topEntries) { if (name === "node_modules" || name === ".git") continue; const full = join5(root, name); let st; try { st = statSync3(full); } catch { continue; } if (st.isDirectory()) walk(full, 1); } return { paths: found }; } function asNonEmptyString(value, field) { if (typeof value !== "string" || value.trim() === "") { throw new Error(`MANIFEST missing required string field: ${field}`); } return value.trim(); } function parseContent(raw) { const out = { ...EMPTY_CONTENT }; if (raw === undefined || raw === null) { throw new Error("MANIFEST missing required object field: content"); } if (typeof raw !== "object" || Array.isArray(raw)) { throw new Error("MANIFEST content must be an object"); } const row = raw; const present = new Set(Object.keys(row)); for (const [key, value] of Object.entries(row)) { const on = value === true; const mapped = LEGACY_CONTENT_KEY[key]; if (mapped !== undefined) { if (on && !present.has(String(mapped))) out[mapped] = true; continue; } out[key] = on; } 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 { data = JSON.parse(raw.replace(/^\uFEFF/, "")); } catch (error) { throw new Error(`MANIFEST is not valid JSON: ${error instanceof Error ? error.message : String(error)}`); } if (data === null || typeof data !== "object" || Array.isArray(data)) { throw new Error("MANIFEST root must be an object"); } const row = data; if (row.schemaVersion !== "1.0") { throw new Error(`unsupported schemaVersion (want "1.0", got ${JSON.stringify(row.schemaVersion)})`); } if (row.packageType !== "operator-subset") { throw new Error(`unsupported packageType (want "operator-subset", got ${JSON.stringify(row.packageType)})`); } const operator = row.operator; if (operator === null || typeof operator !== "object" || Array.isArray(operator)) { throw new Error("MANIFEST missing required object field: operator"); } const op = operator; return { operatorName: asNonEmptyString(op.name, "operator.name"), country: asNonEmptyString(op.country, "operator.country"), version: asNonEmptyString(row.version, "version"), content: parseContent(row.content), paths: parsePaths(row.paths) }; } function resolveKbRoot(kbRoot, maxDepth = 3) { const trimmed = kbRoot.trim(); if (!trimmed) return unconfiguredKbSnapshot(); let root = resolve(trimmed); try { const st = statSync3(root); if (st.isFile()) { if (basename(root).toLowerCase() === "manifest.json") { root = dirname3(root); } else { return errorSnapshot(`kbRoot is not a directory: ${root}`); } } else if (!st.isDirectory()) { return errorSnapshot(`kbRoot is not a directory: ${root}`); } } catch { return errorSnapshot(`kbRoot not found: ${root}`); } const located = findManifest(root, maxDepth); if (located.error) return errorSnapshot(located.error); if (located.paths.length === 0) { return errorSnapshot(`no MANIFEST.json under ${root} (maxDepth=${maxDepth})`); } if (located.paths.length > 1) { return errorSnapshot(`ambiguous MANIFEST.json (${located.paths.length} hits); pick a unique package root`); } const manifestPath = located.paths[0]; let raw; try { raw = readFileSync4(manifestPath, "utf8"); } catch (error) { return errorSnapshot(`cannot read MANIFEST: ${error instanceof Error ? error.message : String(error)}`); } try { const parsed = parseManifest(raw); return { status: "configured", realRoot: dirname3(manifestPath), operatorName: parsed.operatorName, country: parsed.country, version: parsed.version, content: parsed.content, paths: { ...parsed.paths }, errorMessage: "" }; } catch (error) { return errorSnapshot(error instanceof Error ? error.message : String(error)); } } function resolveKbPaths(snapshot) { if (snapshot.status !== "configured") return []; const paths = { skills: "_skills", ...snapshot.paths }; return Object.entries(paths).filter((entry) => typeof entry[1] === "string" && entry[1].trim() !== "").map(([key, rel]) => { const absolute = resolve(snapshot.realRoot, rel); return { key, relative: rel, absolute, exists: existsSync4(absolute) }; }); } function renderKbPathTable(snapshot) { const rows = resolveKbPaths(snapshot); if (rows.length === 0) return "(MANIFEST has no paths — legacy package; ask HQ for a repack)"; return [ "| paths.* | Absolute path |", "| --- | --- |", ...rows.map((row) => `| ${row.key} | \`${row.absolute}\`${row.exists ? "" : " (missing)"} |`) ].join(` `); } // src/netx/kb-local-path.ts import { existsSync as existsSync5, realpathSync, statSync as statSync4 } from "node:fs"; import { join as join6, relative, resolve as resolve2, sep } from "node:path"; var KB_LOCAL_WRITABLE_ROOTS = Object.freeze([ "memories", "drafts", "suggestions", "refs" ]); var REF_AREAS = Object.freeze([ "inventory", "devices", "topology", "business", "commands", "handbooks" ]); var MEMORY_BUCKET_DIR = Object.freeze({ note: "日常笔记", rca_review: "排障复盘", ai_trace: "AI思维链" }); function resolveKbLocalRoot(snapshot) { if (snapshot.status !== "configured") return null; const rel = snapshot.paths.local?.trim(); if (!rel) return null; return resolve2(snapshot.realRoot, rel); } function sanitizeSlug(raw, fallback = "entry") { const cleaned = raw.trim().replace(/[/\\]+/g, "-").replace(/\.\.+/g, "").replace(/[^\w.\u4e00-\u9fff-]+/gu, "-").replace(/-+/g, "-").replace(/^-|-$/g, ""); return cleaned || fallback; } function todayIsoDate(now = new Date) { const y = now.getFullYear(); const m = String(now.getMonth() + 1).padStart(2, "0"); const d = String(now.getDate()).padStart(2, "0"); return `${y}-${m}-${d}`; } function toCompactDate(isoOrEmpty, now = new Date) { const iso = (isoOrEmpty?.trim() || todayIsoDate(now)).replace(/-/g, ""); if (!/^\d{8}$/.test(iso)) { throw new Error(`invalid date (want YYYY-MM-DD or YYYYMMDD): ${isoOrEmpty}`); } return iso; } function normalizeIsoDate(raw, now = new Date) { const t = raw?.trim(); if (!t) return todayIsoDate(now); if (/^\d{4}-\d{2}-\d{2}$/.test(t)) return t; if (/^\d{8}$/.test(t)) { return `${t.slice(0, 4)}-${t.slice(4, 6)}-${t.slice(6, 8)}`; } throw new Error(`invalid date (want YYYY-MM-DD or YYYYMMDD): ${raw}`); } function memoryFileName(opts) { const date = normalizeIsoDate(opts.date, opts.now); return `${date}-${sanitizeSlug(opts.slug)}.md`; } function draftFileName(opts) { const compact = toCompactDate(opts.date, opts.now); const slug = sanitizeSlug(opts.slug); const base = slug.toUpperCase().startsWith("DRAFT-") ? slug : `DRAFT-${compact}-${slug}`; return base.toLowerCase().endsWith(".md") ? base : `${base}.md`; } function suggestionFileName(opts) { const date = normalizeIsoDate(opts.date, opts.now); return `${date}-${sanitizeSlug(opts.slug)}.md`; } function refFileName(slug) { const base = sanitizeSlug(slug, "note"); return base.toLowerCase().endsWith(".md") ? base : `${base}.md`; } function resolveWritableLocalPath(localRoot, ...segments) { if (segments.some((s) => s.includes("\x00"))) { throw new Error("path segment contains NUL"); } const absolutePath = resolve2(localRoot, ...segments); const rel = relative(localRoot, absolutePath); if (!rel || rel.startsWith("..") || rel.split(/[/\\]/).includes("..")) { throw new Error(`path escapes local root: ${absolutePath}`); } const top = rel.split(/[/\\]/)[0]; if (!KB_LOCAL_WRITABLE_ROOTS.includes(top)) { throw new Error(`writes only allowed under ${KB_LOCAL_WRITABLE_ROOTS.join("|")}/ (got ${top}/)`); } return { absolutePath, relativePath: rel.split(sep).join("/"), writableRoot: top }; } function resolveExistingWritableFile(localRoot, pathArg) { const trimmed = pathArg.trim(); if (!trimmed) throw new Error("path is empty"); const candidate = resolve2(trimmed); let absolutePath; const localResolved = resolve2(localRoot); const underLocal = candidate === localResolved || candidate.startsWith(localResolved + sep); if (underLocal) { absolutePath = candidate; } else { absolutePath = resolve2(localRoot, trimmed); } const checked = resolveWritableLocalPath(localRoot, ...relative(localRoot, absolutePath).split(/[/\\]/).filter(Boolean)); try { if (existsSync5(checked.absolutePath)) { const realFile = realpathSync(checked.absolutePath); const realLocal = realpathSync(localRoot); const realRel = relative(realLocal, realFile); if (!realRel || realRel.startsWith("..") || realRel.split(/[/\\]/).includes("..")) { throw new Error(`path escapes local root after realpath: ${realFile}`); } const top = realRel.split(/[/\\]/)[0]; if (!KB_LOCAL_WRITABLE_ROOTS.includes(top)) { throw new Error(`path not under writable roots: ${realRel}`); } if (!statSync4(realFile).isFile()) { throw new Error(`not a file: ${realFile}`); } return { absolutePath: realFile, relativePath: realRel.split(sep).join("/"), writableRoot: top }; } } catch (error) { if (error instanceof Error && /escapes|writable|not a file/.test(error.message)) { throw error; } } return checked; } function memoryDir(bucket) { return join6("memories", MEMORY_BUCKET_DIR[bucket]); } function suggestionDir(kind) { return join6("suggestions", kind); } function draftDir(domain) { const d = domain?.trim(); if (!d) return "drafts"; return join6("drafts", sanitizeSlug(d, "misc")); } function refDir(area, device) { if (!REF_AREAS.includes(area)) { throw new Error(`invalid refs area: ${area}`); } if (area === "devices") { const host = sanitizeSlug(device?.trim() || "", ""); if (!host) { throw new Error("refs/devices requires device (host_name)"); } return join6("refs", "devices", host); } return join6("refs", area); } // src/netx/kb-context-skill.ts var SKILL_NAME = "kb-context"; var IDENTITY_FILE_MAX = 8000; var IDENTITY_TOTAL_MAX = 24000; function readKbIdentity(snapshot) { const localRoot = resolveKbLocalRoot(snapshot); if (!localRoot) return []; const dir = join7(localRoot, "identity"); let names; try { names = readdirSync3(dir).filter((name) => /\.md$/i.test(name) && !/^readme\.md$/i.test(name)).sort(); } catch { return []; } const out = []; let budget = IDENTITY_TOTAL_MAX; for (const name of names) { if (budget <= 0) break; const full = join7(dir, name); let text; try { if (!statSync5(full).isFile()) continue; text = readFileSync5(full, "utf8").replace(/^\uFEFF/, "").trim(); } catch { continue; } if (!text) continue; const cap = Math.min(IDENTITY_FILE_MAX, budget); if (text.length > cap) text = `${text.slice(0, cap)} …(truncated — read \`${full}\` for the rest)`; budget -= text.length; out.push({ name, text }); } return out; } function skillBody(snapshot) { if (snapshot.status === "configured") { const flags = Object.entries(snapshot.content).filter(([, on]) => on).map(([key]) => key).join(", ") || "(none)"; const localRoot = resolveKbLocalRoot(snapshot); const localRow = localRoot ? `| kbLocal | \`${localRoot}\` |` : "| kbLocal | (MANIFEST paths.local missing) |"; const identity = readKbIdentity(snapshot); const identityBlock = identity.length > 0 ? [ "", "### Site identity (`identity/`, authoritative — follow over generic advice)", ...identity.flatMap(({ name, text }) => ["", `#### ${name}`, "", text]) ] : []; const localGuide = localRoot ? [ "", "### Dual plane (mandatory)", "", "- **HQ pack = read-only** (formal RCA, theory, packet, `_common`, `_skills`, shared cmdLib).", `- **Writable evolution core**: \`${localRoot}\` → \`refs/\` | \`memories/\` | \`drafts/\` | \`suggestions/\`.`, "- **All KB writes via host tools** (workspace Write/bash cannot reach KB):", " `netx__kbWriteRef` / `netx__kbWriteMemory` / `netx__kbWriteDraft` / `netx__kbWriteSuggestion` /", " `netx__kbUpdateLocal` / `netx__kbDeleteLocal` / `netx__kbListLocal`.", "", "### refs — site product knowledge (evolve every task)", "- On any NE: **read** `refs/devices//` first; if missing, prove with netx then `kbWriteRef` to create/update.", "- `area=devices` requires `device=` (never UUID); primary file slug=`PROFILE`.", "- Also: `inventory` (ledger), `topology`, `business`, `commands` (site-only), `handbooks`.", "- Device facts → refs; episodic experience → memories (do not dump ledgers into diaries).", "", "### memories — write immediately when valuable", "- `bucket=note` | `rca_review` | `ai_trace` — short beats lost.", "- Open cases → `kbWriteDraft`. HQ pack gaps → `kbWriteSuggestion`." ] : [ "", "No local write tools until `paths.local` is present in MANIFEST." ]; return { description: `Load FIRST before any kb-* skill or KB file access: resolved absolute paths (rca / theory / packet / neIndex / cmdLib / local …) for operator ${snapshot.operatorName} (${snapshot.country}) KB v${snapshot.version}, site identity rules, and KB write rules.`, 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}\` |`, localRow, `| kbOperator | ${snapshot.operatorName} |`, `| kbCountry | ${snapshot.country} |`, `| kbVersion | ${snapshot.version} |`, `| kbContent (on) | ${flags} |`, "", "### Path anchors (resolved from MANIFEST `paths.*` — use verbatim, never compose `regions///…`)", "", renderKbPathTable(snapshot), ...identityBlock, ...localGuide, "", "### Reading the HQ pack", "- `netx__kbSearch` — keyword/regex search across rca / theory / packet / common / local (returns file + line hits).", "- `netx__kbRead` — read one KB file (path relative to kbRoot or absolute inside it; optional line window).", "- Start from the indexes: `neIndex` (NE → cases), `patternMap`, `classification`, then open the cited RCA.", "", "Environment mirrors: KB_ROOT, KB_LOCAL, KB_OPERATOR, KB_COUNTRY, KB_VERSION, KB_CONTENT, KB_PATHS (JSON of the anchors above), KB_STATUS.", "", "Business playbooks (kb-troubleshoot, kb-retrieve, …) register from 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(` `) }; } 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-local-tools.ts import { defineTool } from "@deepseek-ai/dsh-tools"; // src/netx/kb-local-ops.ts import { existsSync as existsSync6, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync6, rmSync as rmSync2, statSync as statSync6, writeFileSync as writeFileSync4 } from "node:fs"; import { dirname as dirname4, join as join8 } from "node:path"; function requireLocalRoot(snapshot) { const root = resolveKbLocalRoot(snapshot); if (!root) { throw new Error("KB local root unavailable (configure kbRoot + MANIFEST paths.local)"); } return root; } function ensureDraftBody(body) { const trimmed = body.replace(/^\uFEFF/, ""); if (/^---\r?\n[\s\S]*?\r?\nstatus:\s*draft\b/m.test(trimmed) || /^---\r?\n[\s\S]*?\nstatus:\s*["']?draft["']?\s*$/m.test(trimmed)) { return trimmed.endsWith(` `) ? trimmed : `${trimmed} `; } if (trimmed.startsWith("---")) { const end = trimmed.indexOf(` ---`, 3); if (end !== -1) { const fm = trimmed.slice(0, end + 4); const rest = trimmed.slice(end + 4); if (/\nstatus:\s*/.test(fm)) return trimmed.endsWith(` `) ? trimmed : `${trimmed} `; const injected = fm.replace(/^---\r?\n/, `--- status: draft `); const out = `${injected}${rest}`; return out.endsWith(` `) ? out : `${out} `; } } return `--- status: draft --- ${trimmed.endsWith(` `) ? trimmed : `${trimmed} `}`; } function createMemory(snapshot, args) { const localRoot = requireLocalRoot(snapshot); const name = memoryFileName({ date: args.date, slug: args.slug }); const target = resolveWritableLocalPath(localRoot, memoryDir(args.bucket), name); if (existsSync6(target.absolutePath) && !args.overwrite) { throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`); } mkdirSync3(dirname4(target.absolutePath), { recursive: true }); const existed = existsSync6(target.absolutePath); writeFileSync4(target.absolutePath, args.body.endsWith(` `) ? args.body : `${args.body} `, "utf8"); return { ok: true, action: existed ? "updated" : "created", absolutePath: target.absolutePath, relativePath: target.relativePath }; } function createDraft(snapshot, args) { const localRoot = requireLocalRoot(snapshot); const name = draftFileName({ date: args.date, slug: args.slug }); const target = resolveWritableLocalPath(localRoot, draftDir(args.domain), name); if (existsSync6(target.absolutePath) && !args.overwrite) { throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`); } mkdirSync3(dirname4(target.absolutePath), { recursive: true }); const existed = existsSync6(target.absolutePath); writeFileSync4(target.absolutePath, ensureDraftBody(args.body), "utf8"); return { ok: true, action: existed ? "updated" : "created", absolutePath: target.absolutePath, relativePath: target.relativePath }; } function createSuggestion(snapshot, args) { const localRoot = requireLocalRoot(snapshot); const name = suggestionFileName({ date: args.date, slug: args.slug }); const target = resolveWritableLocalPath(localRoot, suggestionDir(args.kind), name); if (existsSync6(target.absolutePath) && !args.overwrite) { throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`); } mkdirSync3(dirname4(target.absolutePath), { recursive: true }); const existed = existsSync6(target.absolutePath); writeFileSync4(target.absolutePath, args.body.endsWith(` `) ? args.body : `${args.body} `, "utf8"); return { ok: true, action: existed ? "updated" : "created", absolutePath: target.absolutePath, relativePath: target.relativePath }; } function createRef(snapshot, args) { const localRoot = requireLocalRoot(snapshot); const name = refFileName(args.slug); const target = resolveWritableLocalPath(localRoot, refDir(args.area, args.device), name); if (existsSync6(target.absolutePath) && !args.overwrite) { throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`); } mkdirSync3(dirname4(target.absolutePath), { recursive: true }); const existed = existsSync6(target.absolutePath); writeFileSync4(target.absolutePath, args.body.endsWith(` `) ? args.body : `${args.body} `, "utf8"); return { ok: true, action: existed ? "updated" : "created", absolutePath: target.absolutePath, relativePath: target.relativePath }; } function updateLocalFile(snapshot, args) { const localRoot = requireLocalRoot(snapshot); const target = resolveExistingWritableFile(localRoot, args.path); if (!existsSync6(target.absolutePath)) { throw new Error(`file not found: ${target.relativePath}`); } const body = target.writableRoot === "drafts" ? ensureDraftBody(args.body) : args.body.endsWith(` `) ? args.body : `${args.body} `; writeFileSync4(target.absolutePath, body, "utf8"); return { ok: true, action: "updated", absolutePath: target.absolutePath, relativePath: target.relativePath }; } function deleteLocalFile(snapshot, args) { const localRoot = requireLocalRoot(snapshot); const target = resolveExistingWritableFile(localRoot, args.path); if (!existsSync6(target.absolutePath)) { throw new Error(`file not found: ${target.relativePath}`); } rmSync2(target.absolutePath, { force: false }); return { ok: true, action: "deleted", absolutePath: target.absolutePath, relativePath: target.relativePath }; } function walkFiles(dir, baseLocal, out) { let entries; try { entries = readdirSync4(dir); } catch { return; } for (const name of entries) { if (name === "." || name === "..") continue; const full = join8(dir, name); let st; try { st = statSync6(full); } catch { continue; } if (st.isDirectory()) { walkFiles(full, baseLocal, out); } else if (st.isFile() && name.toLowerCase().endsWith(".md")) { const rel = full.slice(baseLocal.length).replace(/^[/\\]/, "").split(/[/\\]/).join("/"); out.push({ absolutePath: full, relativePath: rel, mtimeMs: st.mtimeMs, size: st.size }); } } } function listLocalFiles(snapshot, args = {}) { const localRoot = requireLocalRoot(snapshot); const roots = args.root && args.root !== "all" ? [args.root] : [...KB_LOCAL_WRITABLE_ROOTS]; const entries = []; for (const r of roots) { walkFiles(join8(localRoot, r), localRoot, entries); } entries.sort((a, b) => b.mtimeMs - a.mtimeMs); const limit = Math.min(Math.max(args.limit ?? 50, 1), 200); return { ok: true, localRoot, entries: entries.slice(0, limit) }; } // src/netx/kb-read-ops.ts import { readdirSync as readdirSync5, readFileSync as readFileSync7, realpathSync as realpathSync2, statSync as statSync7 } from "node:fs"; import { extname, join as join9, relative as relative2, resolve as resolve3, sep as sep2 } from "node:path"; var TEXT_EXTENSIONS = new Set([".md", ".markdown", ".txt", ".json", ".yml", ".yaml", ".csv", ".ps1", ".sh", ".cfg", ".conf", ".log"]); var SKIP_DIRS = new Set([".git", "node_modules"]); var READ_DEFAULT_LINES = 400; var READ_MAX_LINES = 2000; var READ_MAX_CHARS = 80000; var SEARCH_MAX_FILE_BYTES = 2 * 1024 * 1024; var SEARCH_HITS_PER_FILE = 4; var CACHE_MAX_CHARS = 64 * 1024 * 1024; function requireRoot(snapshot) { if (snapshot.status !== "configured" || !snapshot.realRoot) { throw new Error("knowledge base not configured (set kbRoot to a folder with MANIFEST.json)"); } return snapshot.realRoot; } function toPosix(rel) { return rel.split(sep2).join("/"); } function isInside(root, target) { const rel = relative2(root, target); return rel === "" || !rel.startsWith("..") && !rel.split(/[/\\]/).includes("..") && !/^[a-zA-Z]:/.test(rel); } function resolveKbReadPath(snapshot, pathArg) { const root = requireRoot(snapshot); const trimmed = pathArg.trim().replace(/^["']|["']$/g, ""); if (trimmed.includes("\x00")) throw new Error("path contains NUL"); const anchor = resolveKbPaths(snapshot).find((row) => row.key === trimmed); const candidate = anchor ? anchor.absolute : !trimmed || trimmed === "." || trimmed === "/" ? root : resolve3(root, trimmed); if (!isInside(root, candidate)) throw new Error(`path escapes kbRoot: ${candidate}`); let real; try { real = realpathSync2(candidate); } catch { throw new Error(`not found in KB: ${toPosix(relative2(root, candidate)) || "."}`); } const realRoot = realpathSync2(root); if (!isInside(realRoot, real)) throw new Error(`path escapes kbRoot after realpath: ${real}`); return { absolutePath: real, relativePath: toPosix(relative2(realRoot, real)) || "." }; } function readKbFile(snapshot, args) { const target = resolveKbReadPath(snapshot, args.path); const st = statSync7(target.absolutePath); if (st.isDirectory()) { const entries = readdirSync5(target.absolutePath, { withFileTypes: true }).filter((entry) => !SKIP_DIRS.has(entry.name)).map((entry) => { if (entry.isDirectory()) return { name: entry.name, type: "dir" }; let size; try { size = statSync7(join9(target.absolutePath, entry.name)).size; } catch {} return { name: entry.name, type: "file", size }; }).sort((a, b) => a.type === b.type ? a.name.localeCompare(b.name) : a.type === "dir" ? -1 : 1); return { ok: true, kind: "directory", ...target, entries }; } if (!TEXT_EXTENSIONS.has(extname(target.absolutePath).toLowerCase())) { throw new Error(`not a text KB file: ${target.relativePath}`); } const lines = readFileSync7(target.absolutePath, "utf8").replace(/^\uFEFF/, "").replace(/\r\n?/g, ` `).split(` `); const start = Math.max(1, Math.floor(args.offset ?? 1)); const limit = Math.min(Math.max(1, Math.floor(args.limit ?? READ_DEFAULT_LINES)), READ_MAX_LINES); let slice = lines.slice(start - 1, start - 1 + limit); let text = slice.join(` `); let truncated = start - 1 + slice.length < lines.length; if (text.length > READ_MAX_CHARS) { let used = 0; let keep = 0; for (const line of slice) { if (used + line.length + 1 > READ_MAX_CHARS) break; used += line.length + 1; keep += 1; } slice = slice.slice(0, Math.max(keep, 1)); text = slice.join(` `).slice(0, READ_MAX_CHARS); truncated = true; } return { ok: true, kind: "file", ...target, totalLines: lines.length, startLine: start, endLine: start - 1 + slice.length, truncated, text }; } var CACHE_KEY = Symbol.for("dsh-netxops.kb-search-cache"); function searchCache() { const root = globalThis; root[CACHE_KEY] ??= { files: new Map, chars: 0 }; return root[CACHE_KEY]; } function cachedText(path, mtimeMs, size) { const cache = searchCache(); const hit = cache.files.get(path); if (hit && hit.mtimeMs === mtimeMs && hit.size === size) return hit; const text = readFileSync7(path, "utf8").replace(/^\uFEFF/, ""); if (hit) cache.chars -= hit.text.length; if (cache.chars + text.length > CACHE_MAX_CHARS) { cache.files.clear(); cache.chars = 0; } const entry = { mtimeMs, size, text, lower: text.toLowerCase() }; cache.files.set(path, entry); cache.chars += text.length; return entry; } function walkTextFiles(dir, out) { let entries; try { entries = readdirSync5(dir, { withFileTypes: true }); } catch { return; } for (const entry of entries) { if (SKIP_DIRS.has(entry.name)) continue; const full = join9(dir, entry.name); if (entry.isDirectory()) { walkTextFiles(full, out); continue; } if (!entry.isFile() || !TEXT_EXTENSIONS.has(extname(entry.name).toLowerCase())) continue; try { const st = statSync7(full); if (st.size <= SEARCH_MAX_FILE_BYTES) out.push({ path: full, mtimeMs: st.mtimeMs, size: st.size }); } catch {} } } function searchKb(snapshot, args) { const root = requireRoot(snapshot); const query = args.query.trim(); if (!query) throw new Error("query is empty"); const scopeArg = args.scope?.trim() || "all"; const scopes = scopeArg.split(/[,\s]+/).filter(Boolean); const dirs = scopes.includes("all") ? [root] : scopes.map((scope) => resolveKbReadPath(snapshot, scope).absolutePath); let matchers; if (args.regex) { let re; try { re = new RegExp(query, "i"); } catch (error) { throw new Error(`invalid regex: ${error instanceof Error ? error.message : String(error)}`); } matchers = [{ term: query, test: (_lower, raw) => re.test(raw) }]; } else { const terms = [...new Set(query.toLowerCase().split(/\s+/).filter(Boolean))]; matchers = terms.map((term) => ({ term, test: (lower) => lower.includes(term) })); } const files = []; const seen = new Set; for (const dir of dirs) { const st = statSync7(dir); const batch = []; if (st.isFile()) batch.push({ path: dir, mtimeMs: st.mtimeMs, size: st.size }); else walkTextFiles(dir, batch); for (const file of batch) { if (seen.has(file.path)) continue; seen.add(file.path); files.push(file); } } const hits = []; for (const file of files) { let cached; try { cached = cachedText(file.path, file.mtimeMs, file.size); } catch { continue; } const rel = toPosix(relative2(root, file.path)); const matched = new Set; let score = 0; for (const m of matchers) { if (m.test(rel.toLowerCase(), rel)) { matched.add(m.term); score += 3; } } const inBody = matchers.filter((m) => m.test(cached.lower, cached.text)); if (inBody.length === 0 && matched.size === 0) continue; const lines = []; const rows = inBody.length > 0 ? cached.text.split(/\r\n?|\n/) : []; let lineHits = 0; for (let i = 0;i < rows.length; i += 1) { const row = rows[i]; const lower = row.toLowerCase(); let lineHit = false; for (const m of inBody) { if (m.test(lower, row)) { matched.add(m.term); lineHit = true; } } if (!lineHit) continue; lineHits += 1; if (lines.length < SEARCH_HITS_PER_FILE) { const trimmed = row.trim(); lines.push({ line: i + 1, text: trimmed.length > 240 ? `${trimmed.slice(0, 240)}…` : trimmed }); } } score += matched.size * 10 + Math.min(lineHits, 30) * 0.1; hits.push({ relativePath: rel, absolutePath: file.path, score: Math.round(score * 10) / 10, matchedTerms: [...matched], lines }); } hits.sort((a, b) => b.score - a.score || a.relativePath.localeCompare(b.relativePath)); const limit = Math.min(Math.max(Math.floor(args.limit ?? 20), 1), 100); return { ok: true, scope: scopeArg, scannedFiles: files.length, totalMatches: hits.length, hits: hits.slice(0, limit) }; } // src/netx/kb-runtime.ts var STORE_KEY3 = Symbol.for("dsh-netxops.kb-store"); var ENV_KEYS = [ "KB_ROOT", "KB_LOCAL", "KB_OPERATOR", "KB_COUNTRY", "KB_VERSION", "KB_CONTENT", "KB_PATHS", "KB_STATUS" ]; function store3() { const root = globalThis; let current = root[STORE_KEY3]; if (current === undefined) { current = { snapshot: unconfiguredKbSnapshot(), listeners: new Set }; root[STORE_KEY3] = current; } return current; } function getKbContext() { const snap = store3().snapshot; return { ...snap, content: { ...snap.content }, paths: { ...snap.paths } }; } function publishKbContext(next, options = {}) { const state = store3(); if (!options.force && JSON.stringify(state.snapshot) === JSON.stringify(next)) return; state.snapshot = { ...next, content: { ...next.content }, paths: { ...next.paths } }; for (const listener of [...state.listeners]) { try { listener(); } catch (error) { console.error("netxops: knowledge subscriber failed", error); } } } function watchKbContext(listener) { const state = store3(); state.listeners.add(listener); return () => { state.listeners.delete(listener); }; } function applyKbEnv(snapshot) { for (const key of ENV_KEYS) { delete process.env[key]; } process.env.KB_STATUS = snapshot.status; if (snapshot.status !== "configured") return; process.env.KB_ROOT = snapshot.realRoot; const local = resolveKbLocalRoot(snapshot); if (local) process.env.KB_LOCAL = local; process.env.KB_OPERATOR = snapshot.operatorName; process.env.KB_COUNTRY = snapshot.country; process.env.KB_VERSION = snapshot.version; process.env.KB_CONTENT = JSON.stringify(snapshot.content); process.env.KB_PATHS = JSON.stringify(Object.fromEntries(resolveKbPaths(snapshot).map((row) => [row.key, row.absolute]))); } function resetKbContext() { publishKbContext(unconfiguredKbSnapshot()); applyKbEnv(unconfiguredKbSnapshot()); } // src/netx/json-safe.ts function toLosslessJson(value) { try { const text = JSON.stringify(value); if (text === undefined) { return { ok: false, error: "tool_result_not_json", detail: "undefined_root" }; } const parsed = JSON.parse(text); if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) { return parsed; } return { ok: true, data: { value: parsed } }; } catch (error) { const detail = error instanceof Error ? error.message : String(error); return { ok: false, error: "tool_result_not_json", detail: detail.slice(0, 400) }; } } // src/netx/tool-mount-status.ts import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "node:fs"; import { homedir as homedir4 } from "node:os"; import { dirname as dirname5, join as join10 } from "node:path"; var KEY = Symbol.for("dsh-netxops.mount-inventory"); function store4() { const root = globalThis; return root[KEY] ??= { mounts: new Map, contexts: new WeakMap, sequence: 0 }; } function getToolMountId(ctx, source) { const state = store4(); let id = state.contexts.get(ctx); if (id === undefined) { id = ++state.sequence; state.contexts.set(ctx, id); } return `${source}:${id}`; } function getToolMountStatuses() { return [...store4().mounts.values()].map((row) => ({ ...row, groups: [...row.groups], names: [...row.names] })); } function getToolMountStatus() { const rows = getToolMountStatuses(); return rows.find((row) => row.plane === "host") ?? rows.at(-1); } function persist() { try { const path = join10(process.env.DSH_HOME?.trim() || join10(homedir4(), ".dsh"), "netxops-mount-status.json"); mkdirSync4(dirname5(path), { recursive: true }); writeFileSync5(path, `${JSON.stringify({ mount: getToolMountStatus() ?? null, mounts: getToolMountStatuses() }, null, 2)} `, "utf8"); } catch {} } function publishToolMountStatus(next) { store4().mounts.set(next.id ?? next.source ?? next.plane, { ...next, groups: [...next.groups], names: [...next.names] }); persist(); } function removeToolMountStatus(id) { store4().mounts.delete(id); persist(); } function resetToolMountStatus() { store4().mounts.clear(); persist(); } // src/netx/kb-local-tools.ts var str = (description) => ({ type: "string", ...description ? { description } : {} }); var bool = (description) => ({ type: "boolean", ...description ? { description } : {} }); var num = (description) => ({ type: "number", ...description ? { description } : {} }); var reqStr = (description) => ({ type: "string", required: true, ...description ? { description } : {} }); function renderJson(_args, value) { return [{ type: "text", text: JSON.stringify(value, null, 0) }]; } var jsonOut = { schema: { type: "object", additionalProperties: true }, render: renderJson }; function liveSnapshot() { return getKbContext(); } function tool(name, description, parameters, execute) { return defineTool({ name, description, parameters, output: jsonOut, timeoutMs: 30000, isConcurrencySafe: () => false, async execute(args) { try { return toLosslessJson(await execute(args)); } catch (error) { throw new Error(error instanceof Error ? error.message : String(error)); } } }); } function writeTools(localRoot) { return [ tool("netx__kbWriteMemory", `Create (or overwrite) one diary-style memory markdown under ${localRoot}/memories/. ` + "bucket: note→日常笔记, rca_review→排障复盘, ai_trace→AI思维链. " + "Default create-only; set overwrite=true to replace same filename. " + "Do not use workspace Write — KB is outside the sandbox.", { bucket: { type: "string", required: true, enum: ["note", "rca_review", "ai_trace"], description: "Memory subdirectory" }, slug: reqStr("Short filename stem (no path separators)"), body: reqStr("Full markdown body"), date: str("Optional YYYY-MM-DD (default today)"), overwrite: bool("Replace if the target file already exists") }, (args) => createMemory(liveSnapshot(), { bucket: args.bucket, slug: String(args.slug ?? ""), body: String(args.body ?? ""), date: args.date != null ? String(args.date) : undefined, overwrite: args.overwrite === true })), tool("netx__kbWriteDraft", `Create (or overwrite) a DRAFT case under ${localRoot}/drafts/. ` + "Filename is forced to DRAFT-YYYYMMDD-…; body gets status: draft frontmatter if missing. " + "Set overwrite=true to replace. Not for formal RCA.", { slug: reqStr("Case stem (DRAFT- prefix added if missing)"), body: reqStr("Markdown body (RCA-ish draft)"), domain: str("Optional fault-domain subfolder under drafts/"), date: str("Optional event date YYYY-MM-DD or YYYYMMDD"), overwrite: bool("Replace if the target file already exists") }, (args) => createDraft(liveSnapshot(), { slug: String(args.slug ?? ""), body: String(args.body ?? ""), domain: args.domain != null ? String(args.domain) : undefined, date: args.date != null ? String(args.date) : undefined, overwrite: args.overwrite === true })), tool("netx__kbWriteSuggestion", `Create (or overwrite) a suggestion under ${localRoot}/suggestions/{theory|improvement}/. ` + "Use for HQ pack/theory corrections or tool/process improvements.", { kind: { type: "string", required: true, enum: ["theory", "improvement"], description: "theory = knowledge fix; improvement = tools/skills/process" }, slug: reqStr("Short filename stem"), body: reqStr("Full markdown body"), date: str("Optional YYYY-MM-DD (default today)"), overwrite: bool("Replace if the target file already exists") }, (args) => createSuggestion(liveSnapshot(), { kind: args.kind, slug: String(args.slug ?? ""), body: String(args.body ?? ""), date: args.date != null ? String(args.date) : undefined, overwrite: args.overwrite === true })), tool("netx__kbWriteRef", `Create (or overwrite) site product-knowledge under ${localRoot}/refs/. ` + "area=inventory|devices|topology|business|commands|handbooks. " + "For devices: require device=host_name; default slug PROFILE for the ops profile. " + "Use for NE ledger, per-device profiles, topology/business notes, site command books — not diaries.", { area: { type: "string", required: true, enum: ["inventory", "devices", "topology", "business", "commands", "handbooks"], description: "refs/ subtree" }, slug: reqStr("Filename stem (e.g. PROFILE, neighbors, ledger)"), body: reqStr("Full markdown body"), device: str("Required when area=devices — host_name (never UUID)"), overwrite: bool("Replace if the target file already exists") }, (args) => createRef(liveSnapshot(), { area: args.area, slug: String(args.slug ?? ""), body: String(args.body ?? ""), device: args.device != null ? String(args.device) : undefined, overwrite: args.overwrite === true })), tool("netx__kbUpdateLocal", `Replace the body of an existing file under memories|drafts|suggestions|refs ` + `(absolute path or path relative to ${localRoot}). ` + "Drafts keep status: draft.", { path: reqStr("Absolute path or path relative to KB local root"), body: reqStr("New full markdown body") }, (args) => updateLocalFile(liveSnapshot(), { path: String(args.path ?? ""), body: String(args.body ?? "") })), tool("netx__kbDeleteLocal", `Delete one existing markdown under memories|drafts|suggestions|refs ` + `(absolute or relative to ${localRoot}).`, { path: reqStr("Absolute path or path relative to KB local root") }, (args) => deleteLocalFile(liveSnapshot(), { path: String(args.path ?? "") })), tool("netx__kbListLocal", `List recent .md files under ${localRoot} memories|drafts|suggestions|refs (newest first). ` + "Read contents with netx__kbRead using the listed paths.", { root: { type: "string", enum: ["memories", "drafts", "suggestions", "refs", "all"], description: "Subtree to list (default all writable)" }, limit: num("Max entries 1–200 (default 50)") }, (args) => listLocalFiles(liveSnapshot(), { root: args.root ?? "all", limit: typeof args.limit === "number" ? args.limit : undefined })) ]; } function readTools(snapshot) { return [ tool("netx__kbSearch", `Search the operator knowledge base at ${snapshot.realRoot} (read-only HQ pack + _local). ` + "Plain mode: space-separated keywords (NE host_name, alarm, protocol, symptom…), case-insensitive; " + "files ranked by how many distinct terms match (filename hits weigh more), up to 4 hit lines each. " + "Then open the best hits with netx__kbRead.", { query: reqStr("Keywords (space-separated) or a regex when regex=true"), scope: str("MANIFEST paths key(s) — e.g. rca, theory, theoryVendor, packet, commonPatterns, local, neIndex — or a relative dir; comma-separate several; default all"), regex: bool("Treat query as one case-insensitive JS regex"), limit: num("Max files 1–100 (default 20)") }, (args) => searchKb(liveSnapshot(), { query: String(args.query ?? ""), scope: args.scope != null ? String(args.scope) : undefined, regex: args.regex === true, limit: typeof args.limit === "number" ? args.limit : undefined })), tool("netx__kbRead", `Read one file (or list one directory) inside the knowledge base at ${snapshot.realRoot}. ` + "path: relative to kbRoot, absolute inside it, or a MANIFEST paths key (rca, neIndex, cmdLib, patternMap, …). " + "Long files: page with offset/limit (1-based lines; default 400).", { path: reqStr("KB-relative path, absolute path inside kbRoot, or a paths.* key"), offset: num("First line (1-based, default 1)"), limit: num("Max lines 1–2000 (default 400)") }, (args) => readKbFile(liveSnapshot(), { path: String(args.path ?? ""), offset: typeof args.offset === "number" ? args.offset : undefined, limit: typeof args.limit === "number" ? args.limit : undefined })) ]; } function registerKbLocalTools(ctx, snapshot = getKbContext(), options = {}) { if (snapshot.status !== "configured") return () => {}; const localRoot = resolveKbLocalRoot(snapshot); const toolsApi = ctx.tools; if (!toolsApi || typeof toolsApi.register !== "function") return () => {}; const entries = [...readTools(snapshot), ...localRoot ? writeTools(localRoot) : []]; const disposers = []; const source = options.source ?? "netxops-kb-local"; const id = getToolMountId(ctx, source); const status = { id, source, version: getToolMountStatus()?.version ?? "", plane: options.plane ?? "preset", groups: ["kb"], at: new Date().toISOString() }; try { for (const entry of entries) disposers.push(toolsApi.register(entry)); publishToolMountStatus({ ...status, names: entries.map((entry) => entry.name), count: entries.length }); } catch (error) { for (const dispose of disposers.reverse()) dispose(); publishToolMountStatus({ ...status, names: [], count: 0, error: String(error) }); throw error; } ctx.logger.info("netxops: kb tools registered count=%s kbRoot=%s localRoot=%s", disposers.length, snapshot.realRoot, localRoot ?? "(none — read-only)"); return () => { for (const dispose of disposers) dispose(); removeToolMountStatus(id); }; } // src/netx/kb-pack-skills.ts import { readdir as readdir2, stat as stat2 } from "node:fs/promises"; import { join as join11 } from "node:path"; function kbPackSkillsEnabled(snapshot) { return snapshot.status === "configured" && snapshot.content.hasSkills === true; } function resolveKbSkillsRoot(snapshot) { const rel = snapshot.paths.skills?.trim() || "_skills"; return join11(snapshot.realRoot, rel); } function kbPackSkillFooter(snapshot) { return [ "", "", `## Injected KB anchors (netxops · ${snapshot.operatorName} / ${snapshot.country} v${snapshot.version})`, "", `kbStatus=configured · kbRoot=\`${snapshot.realRoot}\``, "", renderKbPathTable(snapshot), "", "These are already resolved from `MANIFEST.json` `paths.*`; use them verbatim.", "Read/search the pack with `netx__kbRead` / `netx__kbSearch` when present; write only via `netx__kbWrite*`.", "" ].join(` `); } function resolveKbLocalSkillsRoot(snapshot) { const rel = snapshot.paths.localSkills?.trim(); if (!rel) return null; return join11(snapshot.realRoot, rel); } async function registerSkillsUnderRoot(ctx, skillsApi, root, provider, options) { let entries; try { entries = await readdir2(root); } catch { 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 = []; try { for (const entry of entries) { if (options.signal?.aborted) break; const full = join11(root, entry); let isDir = false; try { isDir = (await stat2(full)).isDirectory(); } catch { continue; } if (!isDir) continue; let skill; try { skill = await loadSkillBundle(full); } catch (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 %s skill skipped (invalid SKILL.md): %s", options.label, full); continue; } if (options.signal?.aborted) break; if (options.seen.has(skill.name)) { ctx.logger.warn("netxops: kb %s skill %s duplicates an already registered pack skill — skip %s", options.label, skill.name, full); continue; } try { disposers.push(skillsApi.register({ name: skill.name, description: skill.description, content: `${skill.content.trimEnd()}${options.footer}`, path: skill.path, resourceBase: { kind: "directory", path: skill.directory }, provider, source: "custom" })); options.seen.add(skill.name); } catch (error) { ctx.logger.warn("netxops: kb %s skill %s register failed: %s", options.label, skill.name, error instanceof Error ? error.message : String(error)); } } } catch (error) { for (const dispose of disposers.splice(0)) dispose(); throw error; } 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); const localRoot = resolveKbLocalSkillsRoot(snapshot); const footer = kbPackSkillFooter(snapshot); const seen = new Set; try { disposers.push(...await registerSkillsUnderRoot(ctx, skillsApi, skillsRoot, provider, { logMissing: true, label: "pack", signal: options.signal, footer, seen })); if (localRoot) { disposers.push(...await registerSkillsUnderRoot(ctx, skillsApi, localRoot, provider, { logMissing: false, label: "local", signal: options.signal, footer, seen })); } } catch (error) { for (const dispose of disposers.splice(0)) dispose(); throw error; } if (options.signal?.aborted) { for (const dispose of disposers.splice(0)) dispose(); } if (disposers.length > 0) { 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) dispose(); }; } // src/netx/model-language.ts var THINKING_LANGUAGE_DEFAULT = "auto"; var REPLY_LANGUAGE_DEFAULT = "follow-user"; var THINKING_LANGUAGE_IDS = ["zh-CN", "en"]; var REPLY_LANGUAGE_IDS = ["zh", "en"]; var THINKING_META = { "zh-CN": { id: "zh-CN", name: "Simplified Chinese", native: "简体中文" }, en: { id: "en", name: "English", native: "English" } }; var REPLY_META = { zh: { id: "zh", name: "Simplified Chinese", native: "简体中文" }, en: { id: "en", name: "English", native: "English" } }; function normalizeThinkingLanguage(raw) { const value = String(raw ?? "").trim(); if (!value || value === THINKING_LANGUAGE_DEFAULT) return THINKING_LANGUAGE_DEFAULT; const lower = value.toLowerCase().replace(/_/g, "-"); if (lower === "zh" || lower === "zh-cn" || lower === "zh-hans" || lower === "chinese") return "zh-CN"; if (lower === "en" || lower.startsWith("en-") || lower === "english") return "en"; if (THINKING_LANGUAGE_IDS.includes(value)) { return value; } return THINKING_LANGUAGE_DEFAULT; } function normalizeReplyLanguage(raw) { const value = String(raw ?? "").trim(); if (!value || value === REPLY_LANGUAGE_DEFAULT || value === "auto" || value === "default") { return REPLY_LANGUAGE_DEFAULT; } const lower = value.toLowerCase().replace(/_/g, "-"); if (lower === "zh" || lower === "zh-cn" || lower === "zh-hans" || lower === "chinese") return "zh"; if (lower === "en" || lower.startsWith("en-") || lower === "english") return "en"; if (REPLY_LANGUAGE_IDS.includes(value)) { return value; } return REPLY_LANGUAGE_DEFAULT; } function resolveThinkingLanguage(setting, systemLocale) { const normalized = normalizeThinkingLanguage(setting); if (normalized !== THINKING_LANGUAGE_DEFAULT) return normalized; return languageFromLocale(systemLocale) ?? "en"; } function languageFromLocale(locale) { const tag = String(locale ?? "").trim().toLowerCase().replace(/_/g, "-"); if (!tag) return; if (tag === "zh" || tag.startsWith("zh-hans") || tag.startsWith("zh-cn") || tag === "zh-sg") { return "zh-CN"; } if (tag.startsWith("zh")) return "zh-CN"; if (tag === "en" || tag.startsWith("en-")) return "en"; return; } function readSystemLocalePreference(settings) { if (!settings || typeof settings.get !== "function") return; let section; try { section = settings.get("locale"); } catch { return; } if (section === null || typeof section !== "object" || Array.isArray(section)) return; const preference = section.preference; return typeof preference === "string" && preference.trim() ? preference.trim() : undefined; } function thinkingInstruction(language) { const meta = THINKING_META[language]; return [ `The operator configured the language of your thinking/reasoning process: ${meta.native} (${meta.name}).`, `Write your entire internal reasoning — chain-of-thought, analysis, planning, and deliberation — in ${meta.name}.`, "Keep code, identifiers, file paths, commands, and technical terms as they are.", "Your final answer to the user follows the reply-language setting (or the user language when reply language is follow-user); this thinking setting never alone changes the final answer language." ].join(" "); } function thinkingReminder(language) { const meta = THINKING_META[language]; return [ `[Thinking language] The operator wants your reasoning/thinking process written in ${meta.native} (${meta.name}).`, `Continue your entire chain-of-thought in ${meta.name}.`, "Final answer language follows the reply-language setting / user language." ].join(" "); } function replyInstruction(setting) { const normalized = normalizeReplyLanguage(setting); if (normalized === REPLY_LANGUAGE_DEFAULT) return ""; const meta = REPLY_META[normalized]; const noGloss = normalized === "en" ? 'Never add Chinese glosses or bilingual parentheses such as "communication failure (失联)" or "alarm (告警)"; use one English wording only.' : 'Never add English glosses or bilingual parentheses such as "失联 (communication failure)" or "告警 (alarm)"; use one Chinese wording only.'; return [ "## Reply language requirement (mandatory)", `The operator set Netx Ops reply language to ${meta.native} (${meta.name}).`, `You MUST write the entire final reply in ${meta.name} only.`, `Do not switch to the user's language even if they write in Chinese, English, or any other language.`, "This includes titles, bullet labels (Result / Evidence / Next may stay as English keywords), explanations, summaries, warnings, and error text.", noGloss, "Keep code, commands, file paths, API names, host names, UUIDs, and proper nouns unchanged.", 'This rule outranks persona wording such as "follow the user language" and outranks language hints in the user message.' ].join(" "); } function replyReminder(setting) { const normalized = normalizeReplyLanguage(setting); if (normalized === REPLY_LANGUAGE_DEFAULT) return ""; const meta = REPLY_META[normalized]; const noGloss = normalized === "en" ? "No Chinese words or (中文) glosses in the answer." : "No English glosses like (English term) in the answer."; return [ `[Reply language] Mandatory: write your entire final answer in ${meta.native} (${meta.name}).`, noGloss, "Code/paths/host names stay original." ].join(" "); } // src/netx/session-export.ts import { homedir as homedir5, hostname as osHostname } from "node:os"; // node_modules/.pnpm/fflate@0.8.3/node_modules/fflate/esm/index.mjs import { createRequire as createRequire3 } from "module"; var require2 = createRequire3("/"); var _a; var Worker; var isMarkedAsUntransferable; try { _a = require2("worker_threads"), Worker = _a.Worker, isMarkedAsUntransferable = _a.isMarkedAsUntransferable; } catch (e) {} var u8 = Uint8Array; var u16 = Uint16Array; var i32 = Int32Array; var fleb = new u8([0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0, 0]); var fdeb = new u8([0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 0, 0]); var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]); var freb = function(eb, start) { var b = new u16(31); for (var i = 0;i < 31; ++i) { b[i] = start += 1 << eb[i - 1]; } var r = new i32(b[30]); for (var i = 1;i < 30; ++i) { for (var j = b[i];j < b[i + 1]; ++j) { r[j] = j - b[i] << 5 | i; } } return { b, r }; }; var _a = freb(fleb, 2); var fl = _a.b; var revfl = _a.r; fl[28] = 258, revfl[258] = 28; var _b = freb(fdeb, 0); var fd = _b.b; var revfd = _b.r; var rev = new u16(32768); for (i = 0;i < 32768; ++i) { x = (i & 43690) >> 1 | (i & 21845) << 1; x = (x & 52428) >> 2 | (x & 13107) << 2; x = (x & 61680) >> 4 | (x & 3855) << 4; rev[i] = ((x & 65280) >> 8 | (x & 255) << 8) >> 1; } var x; var i; var hMap = function(cd, mb, r) { var s = cd.length; var i2 = 0; var l = new u16(mb); for (;i2 < s; ++i2) { if (cd[i2]) ++l[cd[i2] - 1]; } var le = new u16(mb); for (i2 = 1;i2 < mb; ++i2) { le[i2] = le[i2 - 1] + l[i2 - 1] << 1; } var co; if (r) { co = new u16(1 << mb); var rvb = 15 - mb; for (i2 = 0;i2 < s; ++i2) { if (cd[i2]) { var sv = i2 << 4 | cd[i2]; var r_1 = mb - cd[i2]; var v = le[cd[i2] - 1]++ << r_1; for (var m = v | (1 << r_1) - 1;v <= m; ++v) { co[rev[v] >> rvb] = sv; } } } } else { co = new u16(s); for (i2 = 0;i2 < s; ++i2) { if (cd[i2]) { co[i2] = rev[le[cd[i2] - 1]++] >> 15 - cd[i2]; } } } return co; }; var flt = new u8(288); for (i = 0;i < 144; ++i) flt[i] = 8; var i; for (i = 144;i < 256; ++i) flt[i] = 9; var i; for (i = 256;i < 280; ++i) flt[i] = 7; var i; for (i = 280;i < 288; ++i) flt[i] = 8; var i; var fdt = new u8(32); for (i = 0;i < 32; ++i) fdt[i] = 5; var i; var flm = /* @__PURE__ */ hMap(flt, 9, 0); var fdm = /* @__PURE__ */ hMap(fdt, 5, 0); var shft = function(p) { return (p + 7) / 8 | 0; }; var slc = function(v, s, e) { if (s == null || s < 0) s = 0; if (e == null || e > v.length) e = v.length; return new u8(v.subarray(s, e)); }; var ec = [ "unexpected EOF", "invalid block type", "invalid length/literal", "invalid distance", "stream finished", "no stream handler", , "no callback", "invalid UTF-8 data", "extra field too long", "date not in range 1980-2099", "filename too long", "stream finishing", "invalid zip data" ]; var err = function(ind, msg, nt) { var e = new Error(msg || ec[ind]); e.code = ind; if (Error.captureStackTrace) Error.captureStackTrace(e, err); if (!nt) throw e; return e; }; var wbits = function(d, p, v) { v <<= p & 7; var o = p / 8 | 0; d[o] |= v; d[o + 1] |= v >> 8; }; var wbits16 = function(d, p, v) { v <<= p & 7; var o = p / 8 | 0; d[o] |= v; d[o + 1] |= v >> 8; d[o + 2] |= v >> 16; }; var hTree = function(d, mb) { var t = []; for (var i2 = 0;i2 < d.length; ++i2) { if (d[i2]) t.push({ s: i2, f: d[i2] }); } var s = t.length; var t2 = t.slice(); if (!s) return { t: et, l: 0 }; if (s == 1) { var v = new u8(t[0].s + 1); v[t[0].s] = 1; return { t: v, l: 1 }; } t.sort(function(a, b) { return a.f - b.f; }); t.push({ s: -1, f: 25001 }); var l = t[0], r = t[1], i0 = 0, i1 = 1, i22 = 2; t[0] = { s: -1, f: l.f + r.f, l, r }; while (i1 != s - 1) { l = t[t[i0].f < t[i22].f ? i0++ : i22++]; r = t[i0 != i1 && t[i0].f < t[i22].f ? i0++ : i22++]; t[i1++] = { s: -1, f: l.f + r.f, l, r }; } var maxSym = t2[0].s; for (var i2 = 1;i2 < s; ++i2) { if (t2[i2].s > maxSym) maxSym = t2[i2].s; } var tr = new u16(maxSym + 1); var mbt = ln(t[i1 - 1], tr, 0); if (mbt > mb) { var i2 = 0, dt = 0; var lft = mbt - mb, cst = 1 << lft; t2.sort(function(a, b) { return tr[b.s] - tr[a.s] || a.f - b.f; }); for (;i2 < s; ++i2) { var i2_1 = t2[i2].s; if (tr[i2_1] > mb) { dt += cst - (1 << mbt - tr[i2_1]); tr[i2_1] = mb; } else break; } dt >>= lft; while (dt > 0) { var i2_2 = t2[i2].s; if (tr[i2_2] < mb) dt -= 1 << mb - tr[i2_2]++ - 1; else ++i2; } for (;i2 >= 0 && dt; --i2) { var i2_3 = t2[i2].s; if (tr[i2_3] == mb) { --tr[i2_3]; ++dt; } } mbt = mb; } return { t: new u8(tr), l: mbt }; }; var ln = function(n, l, d) { return n.s == -1 ? Math.max(ln(n.l, l, d + 1), ln(n.r, l, d + 1)) : l[n.s] = d; }; var lc = function(c) { var s = c.length; while (s && !c[--s]) ; var cl = new u16(++s); var cli = 0, cln = c[0], cls = 1; var w = function(v) { cl[cli++] = v; }; for (var i2 = 1;i2 <= s; ++i2) { if (c[i2] == cln && i2 != s) ++cls; else { if (!cln && cls > 2) { for (;cls > 138; cls -= 138) w(32754); if (cls > 2) { w(cls > 10 ? cls - 11 << 5 | 28690 : cls - 3 << 5 | 12305); cls = 0; } } else if (cls > 3) { w(cln), --cls; for (;cls > 6; cls -= 6) w(8304); if (cls > 2) w(cls - 3 << 5 | 8208), cls = 0; } while (cls--) w(cln); cls = 1; cln = c[i2]; } } return { c: cl.subarray(0, cli), n: s }; }; var clen = function(cf, cl) { var l = 0; for (var i2 = 0;i2 < cl.length; ++i2) l += cf[i2] * cl[i2]; return l; }; var wfblk = function(out, pos, dat) { var s = dat.length; var o = shft(pos + 2); out[o] = s & 255; out[o + 1] = s >> 8; out[o + 2] = out[o] ^ 255; out[o + 3] = out[o + 1] ^ 255; for (var i2 = 0;i2 < s; ++i2) out[o + i2 + 4] = dat[i2]; return (o + 4 + s) * 8; }; var wblk = function(dat, out, final, syms, lf, df, eb, li, bs, bl, p) { wbits(out, p++, final); ++lf[256]; var _a2 = hTree(lf, 15), dlt = _a2.t, mlb = _a2.l; var _b2 = hTree(df, 15), ddt = _b2.t, mdb = _b2.l; var _c = lc(dlt), lclt = _c.c, nlc = _c.n; var _d = lc(ddt), lcdt = _d.c, ndc = _d.n; var lcfreq = new u16(19); for (var i2 = 0;i2 < lclt.length; ++i2) ++lcfreq[lclt[i2] & 31]; for (var i2 = 0;i2 < lcdt.length; ++i2) ++lcfreq[lcdt[i2] & 31]; var _e = hTree(lcfreq, 7), lct = _e.t, mlcb = _e.l; var nlcc = 19; for (;nlcc > 4 && !lct[clim[nlcc - 1]]; --nlcc) ; var flen = bl + 5 << 3; var ftlen = clen(lf, flt) + clen(df, fdt) + eb; var dtlen = clen(lf, dlt) + clen(df, ddt) + eb + 14 + 3 * nlcc + clen(lcfreq, lct) + 2 * lcfreq[16] + 3 * lcfreq[17] + 7 * lcfreq[18]; if (bs >= 0 && flen <= ftlen && flen <= dtlen) return wfblk(out, p, dat.subarray(bs, bs + bl)); var lm, ll, dm, dl; wbits(out, p, 1 + (dtlen < ftlen)), p += 2; if (dtlen < ftlen) { lm = hMap(dlt, mlb, 0), ll = dlt, dm = hMap(ddt, mdb, 0), dl = ddt; var llm = hMap(lct, mlcb, 0); wbits(out, p, nlc - 257); wbits(out, p + 5, ndc - 1); wbits(out, p + 10, nlcc - 4); p += 14; for (var i2 = 0;i2 < nlcc; ++i2) wbits(out, p + 3 * i2, lct[clim[i2]]); p += 3 * nlcc; var lcts = [lclt, lcdt]; for (var it = 0;it < 2; ++it) { var clct = lcts[it]; for (var i2 = 0;i2 < clct.length; ++i2) { var len = clct[i2] & 31; wbits(out, p, llm[len]), p += lct[len]; if (len > 15) wbits(out, p, clct[i2] >> 5 & 127), p += clct[i2] >> 12; } } } else { lm = flm, ll = flt, dm = fdm, dl = fdt; } for (var i2 = 0;i2 < li; ++i2) { var sym = syms[i2]; if (sym > 255) { var len = sym >> 18 & 31; wbits16(out, p, lm[len + 257]), p += ll[len + 257]; if (len > 7) wbits(out, p, sym >> 23 & 31), p += fleb[len]; var dst = sym & 31; wbits16(out, p, dm[dst]), p += dl[dst]; if (dst > 3) wbits16(out, p, sym >> 5 & 8191), p += fdeb[dst]; } else { wbits16(out, p, lm[sym]), p += ll[sym]; } } wbits16(out, p, lm[256]); return p + ll[256]; }; var deo = /* @__PURE__ */ new i32([65540, 131080, 131088, 131104, 262176, 1048704, 1048832, 2114560, 2117632]); var et = /* @__PURE__ */ new u8(0); var dflt = function(dat, lvl, plvl, pre, post, st) { var s = st.z || dat.length; var o = new u8(pre + s + 5 * (1 + Math.ceil(s / 7000)) + post); var w = o.subarray(pre, o.length - post); var lst = st.l; var pos = (st.r || 0) & 7; if (lvl) { if (pos) w[0] = st.r >> 3; var opt = deo[lvl - 1]; var n = opt >> 13, c = opt & 8191; var msk_1 = (1 << plvl) - 1; var prev = st.p || new u16(32768), head = st.h || new u16(msk_1 + 1); var bs1_1 = Math.ceil(plvl / 3), bs2_1 = 2 * bs1_1; var hsh = function(i3) { return (dat[i3] ^ dat[i3 + 1] << bs1_1 ^ dat[i3 + 2] << bs2_1) & msk_1; }; var syms = new i32(25000); var lf = new u16(288), df = new u16(32); var lc_1 = 0, eb = 0, i2 = st.i || 0, li = 0, wi = st.w || 0, bs = 0; for (;i2 + 2 < s; ++i2) { var hv = hsh(i2); var imod = i2 & 32767, pimod = head[hv]; prev[imod] = pimod; head[hv] = imod; if (wi <= i2) { var rem = s - i2; if ((lc_1 > 7000 || li > 24576) && (rem > 423 || !lst)) { pos = wblk(dat, w, 0, syms, lf, df, eb, li, bs, i2 - bs, pos); li = lc_1 = eb = 0, bs = i2; for (var j = 0;j < 286; ++j) lf[j] = 0; for (var j = 0;j < 30; ++j) df[j] = 0; } var l = 2, d = 0, ch_1 = c, dif = imod - pimod & 32767; if (rem > 2 && hv == hsh(i2 - dif)) { var maxn = Math.min(n, rem) - 1; var maxd = Math.min(32767, i2); var ml = Math.min(258, rem); while (dif <= maxd && --ch_1 && imod != pimod) { if (dat[i2 + l] == dat[i2 + l - dif]) { var nl = 0; for (;nl < ml && dat[i2 + nl] == dat[i2 + nl - dif]; ++nl) ; if (nl > l) { l = nl, d = dif; if (nl > maxn) break; var mmd = Math.min(dif, nl - 2); var md = 0; for (var j = 0;j < mmd; ++j) { var ti = i2 - dif + j & 32767; var pti = prev[ti]; var cd = ti - pti & 32767; if (cd > md) md = cd, pimod = ti; } } } imod = pimod, pimod = prev[imod]; dif += imod - pimod & 32767; } } if (d) { syms[li++] = 268435456 | revfl[l] << 18 | revfd[d]; var lin = revfl[l] & 31, din = revfd[d] & 31; eb += fleb[lin] + fdeb[din]; ++lf[257 + lin]; ++df[din]; wi = i2 + l; ++lc_1; } else { syms[li++] = dat[i2]; ++lf[dat[i2]]; } } } for (i2 = Math.max(i2, wi);i2 < s; ++i2) { syms[li++] = dat[i2]; ++lf[dat[i2]]; } pos = wblk(dat, w, lst, syms, lf, df, eb, li, bs, i2 - bs, pos); if (!lst) { st.r = pos & 7 | w[pos / 8 | 0] << 3; pos -= 7; st.h = head, st.p = prev, st.i = i2, st.w = wi; } } else { for (var i2 = st.w || 0;i2 < s + lst; i2 += 65535) { var e = i2 + 65535; if (e >= s) { w[pos / 8 | 0] = lst; e = s; } pos = wfblk(w, pos + 1, dat.subarray(i2, e)); } st.i = s; } return slc(o, 0, pre + shft(pos) + post); }; var crct = /* @__PURE__ */ function() { var t = new Int32Array(256); for (var i2 = 0;i2 < 256; ++i2) { var c = i2, k = 9; while (--k) c = (c & 1 && -306674912) ^ c >>> 1; t[i2] = c; } return t; }(); var crc = function() { var c = -1; return { p: function(d) { var cr = c; for (var i2 = 0;i2 < d.length; ++i2) cr = crct[cr & 255 ^ d[i2]] ^ cr >>> 8; c = cr; }, d: function() { return ~c; } }; }; var dopt = function(dat, opt, pre, post, st) { if (!st) { st = { l: 1 }; if (opt.dictionary) { var dict = opt.dictionary.subarray(-32768); var newDat = new u8(dict.length + dat.length); newDat.set(dict); newDat.set(dat, dict.length); dat = newDat; st.w = dict.length; } } return dflt(dat, opt.level == null ? 6 : opt.level, opt.mem == null ? st.l ? Math.ceil(Math.max(8, Math.min(13, Math.log(dat.length))) * 1.5) : 20 : 12 + opt.mem, pre, post, st); }; var mrg = function(a, b) { var o = {}; for (var k in a) o[k] = a[k]; for (var k in b) o[k] = b[k]; return o; }; var wbytes = function(d, b, v) { for (;v; ++b) d[b] = v, v >>>= 8; }; var Deflate = /* @__PURE__ */ function() { function Deflate2(opts, cb) { if (typeof opts == "function") cb = opts, opts = {}; this.ondata = cb; this.o = opts || {}; this.s = { l: 0, i: 32768, w: 32768, z: 32768 }; this.b = new u8(98304); if (this.o.dictionary) { var dict = this.o.dictionary.subarray(-32768); this.b.set(dict, 32768 - dict.length); this.s.i = 32768 - dict.length; } } Deflate2.prototype.p = function(c, f) { this.ondata(dopt(c, this.o, 0, 0, this.s), f); }; Deflate2.prototype.push = function(chunk, final) { if (!this.ondata) err(5); if (this.s.l) err(4); var endLen = chunk.length + this.s.z; if (endLen > this.b.length) { if (endLen > 2 * this.b.length - 32768) { var newBuf = new u8(endLen & -32768); newBuf.set(this.b.subarray(0, this.s.z)); this.b = newBuf; } var split = this.b.length - this.s.z; this.b.set(chunk.subarray(0, split), this.s.z); this.s.z = this.b.length; this.p(this.b, false); this.b.set(this.b.subarray(-32768)); this.b.set(chunk.subarray(split), 32768); this.s.z = chunk.length - split + 32768; this.s.i = 32766, this.s.w = 32768; } else { this.b.set(chunk, this.s.z); this.s.z += chunk.length; } this.s.l = final & 1; if (this.s.z > this.s.w + 8191 || final) { this.p(this.b, final || false); this.s.w = this.s.i, this.s.i -= 2; } if (final) { this.s = this.o = {}; this.b = et; } }; Deflate2.prototype.flush = function(sync) { if (!this.ondata) err(5); if (this.s.l) err(4); this.p(this.b, false); this.s.w = this.s.i, this.s.i -= 2; if (sync) { var c = new u8(6); c[0] = this.s.r >> 3; var ep = wfblk(c, this.s.r, et); this.s.r = 0; this.ondata(c.subarray(0, ep >> 3), false); } }; return Deflate2; }(); var te = typeof TextEncoder != "undefined" && /* @__PURE__ */ new TextEncoder; var td = typeof TextDecoder != "undefined" && /* @__PURE__ */ new TextDecoder; var tds = 0; try { td.decode(et, { stream: true }); tds = 1; } catch (e) {} function strToU8(str2, latin1) { if (latin1) { var ar_1 = new u8(str2.length); for (var i2 = 0;i2 < str2.length; ++i2) ar_1[i2] = str2.charCodeAt(i2); return ar_1; } if (te) return te.encode(str2); var l = str2.length; var ar = new u8(str2.length + (str2.length >> 1)); var ai = 0; var w = function(v) { ar[ai++] = v; }; for (var i2 = 0;i2 < l; ++i2) { if (ai + 5 > ar.length) { var n = new u8(ai + 8 + (l - i2 << 1)); n.set(ar); ar = n; } var c = str2.charCodeAt(i2); if (c < 128 || latin1) w(c); else if (c < 2048) w(192 | c >> 6), w(128 | c & 63); else if (c > 55295 && c < 57344) c = 65536 + (c & 1023 << 10) | str2.charCodeAt(++i2) & 1023, w(240 | c >> 18), w(128 | c >> 12 & 63), w(128 | c >> 6 & 63), w(128 | c & 63); else w(224 | c >> 12), w(128 | c >> 6 & 63), w(128 | c & 63); } return slc(ar, 0, ai); } var dbf = function(l) { return l == 1 ? 3 : l < 6 ? 2 : l == 9 ? 1 : 0; }; var exfl = function(ex) { var le = 0; if (ex) { for (var k in ex) { var l = ex[k].length; if (l > 65535) err(9); le += l + 4; } } return le; }; var wzh = function(d, b, f, fn, u, c, ce, co) { var fl2 = fn.length, ex = f.extra, col = co && co.length; var exl = exfl(ex); wbytes(d, b, ce != null ? 33639248 : 67324752), b += 4; if (ce != null) d[b++] = 20, d[b++] = f.os; d[b] = 20, b += 2; d[b++] = f.flag << 1 | (c < 0 && 8), d[b++] = u && 8; d[b++] = f.compression & 255, d[b++] = f.compression >> 8; var dt = new Date(f.mtime == null ? Date.now() : f.mtime), y = dt.getFullYear() - 1980; if (y < 0 || y > 119) err(10); wbytes(d, b, y << 25 | dt.getMonth() + 1 << 21 | dt.getDate() << 16 | dt.getHours() << 11 | dt.getMinutes() << 5 | dt.getSeconds() >> 1), b += 4; if (c != -1) { wbytes(d, b, f.crc); wbytes(d, b + 4, c < 0 ? -c - 2 : c); wbytes(d, b + 8, f.size); } wbytes(d, b + 12, fl2); wbytes(d, b + 14, exl), b += 16; if (ce != null) { wbytes(d, b, col); wbytes(d, b + 6, f.attrs); wbytes(d, b + 10, ce), b += 14; } d.set(fn, b); b += fl2; if (exl) { for (var k in ex) { var exf = ex[k], l = exf.length; wbytes(d, b, +k); wbytes(d, b + 2, l); d.set(exf, b + 4), b += 4 + l; } } if (col) d.set(co, b), b += col; return b; }; var wzf = function(o, b, c, d, e) { wbytes(o, b, 101010256); wbytes(o, b + 8, c); wbytes(o, b + 10, c); wbytes(o, b + 12, d); wbytes(o, b + 16, e); }; var ZipPassThrough = /* @__PURE__ */ function() { function ZipPassThrough2(filename) { this.filename = filename; this.c = crc(); this.size = 0; this.compression = 0; } ZipPassThrough2.prototype.process = function(chunk, final) { this.ondata(null, chunk, final); }; ZipPassThrough2.prototype.push = function(chunk, final) { if (!this.ondata) err(5); this.c.p(chunk); this.size += chunk.length; if (final) this.crc = this.c.d(); this.process(chunk, final || false); }; return ZipPassThrough2; }(); var ZipDeflate = /* @__PURE__ */ function() { function ZipDeflate2(filename, opts) { var _this = this; if (!opts) opts = {}; ZipPassThrough.call(this, filename); this.d = new Deflate(opts, function(dat, final) { _this.ondata(null, dat, final); }); this.compression = 8; this.flag = dbf(opts.level); } ZipDeflate2.prototype.process = function(chunk, final) { try { this.d.push(chunk, final); } catch (e) { this.ondata(e, null, final); } }; ZipDeflate2.prototype.push = function(chunk, final) { ZipPassThrough.prototype.push.call(this, chunk, final); }; return ZipDeflate2; }(); var Zip = /* @__PURE__ */ function() { function Zip2(cb) { this.ondata = cb; this.u = []; this.d = 1; } Zip2.prototype.add = function(file) { var _this = this; if (!this.ondata) err(5); if (this.d & 2) this.ondata(err(4 + (this.d & 1) * 8, 0, 1), null, false); else { var f = strToU8(file.filename), fl_1 = f.length; var com = file.comment, o = com && strToU8(com); var u = fl_1 != file.filename.length || o && com.length != o.length; var hl_1 = fl_1 + exfl(file.extra) + 30; if (fl_1 > 65535) this.ondata(err(11, 0, 1), null, false); var header = new u8(hl_1); wzh(header, 0, file, f, u, -1); var chks_1 = [header]; var pAll_1 = function() { for (var _i = 0, chks_2 = chks_1;_i < chks_2.length; _i++) { var chk = chks_2[_i]; _this.ondata(null, chk, false); } chks_1 = []; }; var tr_1 = this.d; this.d = 0; var ind_1 = this.u.length; var uf_1 = mrg(file, { f, u, o, t: function() { if (file.terminate) file.terminate(); }, r: function() { pAll_1(); if (tr_1) { var nxt = _this.u[ind_1 + 1]; if (nxt) nxt.r(); else _this.d = 1; } tr_1 = 1; } }); var cl_1 = 0; file.ondata = function(err2, dat, final) { if (err2) { _this.ondata(err2, dat, final); _this.terminate(); } else { cl_1 += dat.length; chks_1.push(dat); if (final) { var dd = new u8(16); wbytes(dd, 0, 134695760); wbytes(dd, 4, file.crc); wbytes(dd, 8, cl_1); wbytes(dd, 12, file.size); chks_1.push(dd); uf_1.c = cl_1, uf_1.b = hl_1 + cl_1 + 16, uf_1.crc = file.crc, uf_1.size = file.size; if (tr_1) uf_1.r(); tr_1 = 1; } else if (tr_1) pAll_1(); } }; this.u.push(uf_1); } }; Zip2.prototype.end = function() { var _this = this; if (this.d & 2) { this.ondata(err(4 + (this.d & 1) * 8, 0, 1), null, true); return; } if (this.d) this.e(); else this.u.push({ r: function() { if (!(_this.d & 1)) return; _this.u.splice(-1, 1); _this.e(); }, t: function() {} }); this.d = 3; }; Zip2.prototype.e = function() { var bt = 0, l = 0, tl = 0; for (var _i = 0, _a2 = this.u;_i < _a2.length; _i++) { var f = _a2[_i]; tl += 46 + f.f.length + exfl(f.extra) + (f.o ? f.o.length : 0); } var out = new u8(tl + 22); for (var _b2 = 0, _c = this.u;_b2 < _c.length; _b2++) { var f = _c[_b2]; wzh(out, bt, f, f.f, f.u, -f.c - 2, l, f.o); bt += 46 + f.f.length + exfl(f.extra) + (f.o ? f.o.length : 0), l += f.b; } wzf(out, bt, this.u.length, tl, l); this.ondata(null, out, true); this.d = 2; }; Zip2.prototype.terminate = function() { for (var _i = 0, _a2 = this.u;_i < _a2.length; _i++) { var f = _a2[_i]; f.t(); } this.d = 2; }; return Zip2; }(); // src/session-export-shared.ts var NETXOPS_SESSIONS_EXPORT_PATH = "/api/netxops.sessions.export"; // src/netx/session-export.ts var COMPRESSION_LEVEL = 6; var PUSH_CHUNK_CODE_UNITS = 1 << 16; function resolveSessionPersistence(ctx) { const fromGet = typeof ctx.get === "function" ? ctx.get("sessionPersistence") : undefined; const persistence = fromGet ?? ctx.sessionPersistence; if (!persistence || typeof persistence.list !== "function" || typeof persistence.readRaw !== "function") { return; } return persistence; } function resolveSessionStore(ctx) { const fromGet = typeof ctx.get === "function" ? ctx.get("sessions") : undefined; const sessions = fromGet ?? ctx.sessions; if (!sessions || typeof sessions.get !== "function" || typeof sessions.flush !== "function") { return; } return sessions; } async function getSessionsExportStatus(ctx, signal) { const persistence = resolveSessionPersistence(ctx); if (!persistence) { return { available: false, sessionCount: 0, supportsRawArtifacts: false, reasonCode: "no_persistence", reason: "sessionPersistence unavailable — mount a JSONL session backend (web profile default)" }; } if (!persistence.supportsRawArtifacts) { return { available: false, sessionCount: 0, supportsRawArtifacts: false, reasonCode: "no_raw_artifacts", reason: "persistence backend does not expose per-session raw artifacts (SQLite export unsupported)" }; } try { const headers = await persistence.list(signal); signal?.throwIfAborted(); return { available: true, sessionCount: headers.length, supportsRawArtifacts: true }; } catch (error) { return { available: false, sessionCount: 0, supportsRawArtifacts: true, reasonCode: "list_failed", reason: error instanceof Error ? error.message : String(error) }; } } function safePathSegment(id) { const base = String(id || "").replace(/\\/g, "/").split("/").pop() ?? ""; const cleaned = base.replace(/[^A-Za-z0-9._-]/g, "_").replace(/^\.+/, ""); return cleaned || "unnamed"; } function sessionsExportZipFilename(exportedAt = new Date, host = osHostname()) { const stamp = [ exportedAt.getUTCFullYear(), String(exportedAt.getUTCMonth() + 1).padStart(2, "0"), String(exportedAt.getUTCDate()).padStart(2, "0"), "-", String(exportedAt.getUTCHours()).padStart(2, "0"), String(exportedAt.getUTCMinutes()).padStart(2, "0"), String(exportedAt.getUTCSeconds()).padStart(2, "0") ].join(""); return `dsh-sessions-${safePathSegment(host)}-${stamp}.zip`; } async function flushLiveSession(sessions, id, signal) { signal?.throwIfAborted(); if (!sessions?.get || !sessions.flush) return; const live = sessions.get(id); if (live === undefined || live === null) return; await sessions.flush(live); signal?.throwIfAborted(); } async function* sessionsExportEntries(ctx, signal) { const persistence = resolveSessionPersistence(ctx); if (!persistence) { throw new Error("sessionPersistence unavailable"); } if (!persistence.supportsRawArtifacts) { throw new Error("persistence backend does not expose per-session raw artifacts"); } const sessions = resolveSessionStore(ctx); const headers = await persistence.list(signal); signal?.throwIfAborted(); const exportedAt = new Date().toISOString(); const host = osHostname(); const included = []; const skipped = []; const artifactEntries = []; for (const header of headers) { signal?.throwIfAborted(); const id = String(header.id); try { await flushLiveSession(sessions, id, signal); const raw = await persistence.readRaw(id, signal); signal?.throwIfAborted(); if (raw === undefined) { skipped.push({ id, reason: "no stored artifact" }); continue; } const filename = safePathSegment(raw.filename && raw.filename.length > 0 ? raw.filename : "session.jsonl") || "session.jsonl"; const path = `sessions/${safePathSegment(id)}/${filename}`; artifactEntries.push({ path, content: raw.content }); included.push({ id, path, createdAt: header.createdAt, cwd: header.cwd, agentPreset: header.agentPreset, parentSession: header.parentSession !== undefined ? String(header.parentSession) : undefined, origin: header.origin }); } catch (error) { skipped.push({ id, reason: error instanceof Error ? error.message : String(error) }); } } const manifest = { kind: "dsh-netxops-sessions-export", version: 1, exportedAt, hostname: host, sessionCountListed: headers.length, sessionCountIncluded: included.length, sessionCountSkipped: skipped.length, sessions: included, skipped }; yield { path: "manifest.json", content: `${JSON.stringify(manifest, null, 2)} ` }; for (const entry of artifactEntries) { signal?.throwIfAborted(); yield entry; } } async function pushArtifactChunks(deflate, content, signal) { const encoder = new TextEncoder; let offset = 0; let finalChunk; do { signal.throwIfAborted(); let end = Math.min(offset + PUSH_CHUNK_CODE_UNITS, content.length); if (end < content.length && end - offset > 1) { const last = content.charCodeAt(end - 1); if (last >= 55296 && last <= 56319) end -= 1; } finalChunk = end >= content.length; deflate.push(encoder.encode(content.slice(offset, end)), finalChunk); offset = end; } while (!finalChunk); } function streamSessionsExportZip(ctx, signal) { const consumerAbort = new AbortController; const producerSignal = AbortSignal.any([signal, consumerAbort.signal]); let zip; let zipTerminated = false; const terminateZip = () => { if (zip === undefined || zipTerminated) return; zipTerminated = true; zip.terminate(); }; return new ReadableStream({ start(controller) { const archive = new Zip((error, data, final) => { if (error) { controller.error(error); return; } if (data.byteLength > 0) controller.enqueue(data); if (final) controller.close(); }); zip = archive; (async () => { try { for await (const entry of sessionsExportEntries(ctx, producerSignal)) { const deflate = new ZipDeflate(entry.path, { level: COMPRESSION_LEVEL }); archive.add(deflate); await pushArtifactChunks(deflate, entry.content, producerSignal); } archive.end(); } catch (error) { terminateZip(); controller.error(error instanceof Error ? error : new Error(String(error))); } })(); }, cancel(reason) { consumerAbort.abort(reason instanceof Error ? reason : new Error("sessions export stream cancelled")); terminateZip(); } }); } async function sessionsExportResponse(ctx, request) { const status = await getSessionsExportStatus(ctx, request.signal); if (!status.available) { return new Response(status.reason ?? "sessions export unavailable", { status: status.supportsRawArtifacts === false && status.reason?.includes("raw artifacts") ? 501 : 500 }); } const filename = sessionsExportZipFilename(); const body = streamSessionsExportZip(ctx, request.signal); return new Response(body, { headers: { "content-type": "application/zip", "content-disposition": `attachment; filename="${filename}"`, "x-netxops-session-count": String(status.sessionCount) } }); } async function sessionsExportHeadResponse(ctx, request) { const status = await getSessionsExportStatus(ctx, request.signal); if (!status.available) { return new Response(null, { status: status.supportsRawArtifacts === false && status.reason?.includes("raw artifacts") ? 501 : 500, headers: { "x-netxops-export-error": status.reason ?? "sessions export unavailable" } }); } const filename = sessionsExportZipFilename(); return new Response(null, { status: 200, headers: { "content-type": "application/zip", "content-disposition": `attachment; filename="${filename}"`, "x-netxops-session-count": String(status.sessionCount) } }); } // src/netx/netxops-web-rpc.ts var NETXOPS_RPC_BODY_MAX = 8 * 1024 * 1024; var ENDPOINT_SEGMENT_PATTERN = /^[A-Za-z0-9_$.-]+$/; var INVALID_REQUEST_RPC_ID = "invalid-request"; var LOOPBACK_HOSTNAMES = new Set(["127.0.0.1", "localhost", "::1"]); function endpointFromPath(channel, pathname) { if (!pathname.startsWith(`${channel}/`)) return; const endpoint = pathname.slice(channel.length + 1); if (endpoint.split("/").some((seg) => seg === "" || seg === "." || seg === ".." || !ENDPOINT_SEGMENT_PATTERN.test(seg))) { return; } return endpoint; } function serverResponseJson(rpcId, result) { return JSON.stringify({ type: "server-response", rpcId, result }); } function isTrustedLoopbackRequest(req) { const host = req.headers?.host; if (!host) return false; const hostName = host.split(":")[0]; if (!LOOPBACK_HOSTNAMES.has(hostName ?? "")) return false; if (req.headers["sec-fetch-site"] === "cross-site") return false; const origin = req.headers.origin; if (origin === undefined) return true; try { return new URL(origin).host === host; } catch { return false; } } function netxopsFetchHandler(channel, handler, log) { return { async fetch(request) { const endpoint = endpointFromPath(channel, new URL(request.url).pathname); if (request.method !== "POST" || endpoint === undefined) { return new Response("not found", { status: 404 }); } const mediaType = request.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase(); if (mediaType !== "application/json") { return new Response("content type must be application/json", { status: 415 }); } let body; try { body = await request.json(); } catch { return new Response("body is not JSON", { status: 400 }); } const row = body; const rpcId = row && typeof row.rpcId === "string" ? row.rpcId : INVALID_REQUEST_RPC_ID; const method = row && typeof row.method === "string" ? row.method : null; if (rpcId === INVALID_REQUEST_RPC_ID || method === null) { return new Response(serverResponseJson(INVALID_REQUEST_RPC_ID, { ok: false, error: { code: "bad-request", message: "invalid client-request message", details: { issues: [] } } }), { status: 200, headers: { "content-type": "application/json" } }); } if (method !== endpoint) { return new Response(serverResponseJson(rpcId, { ok: false, error: { code: "bad-request", message: `method ${JSON.stringify(method)} does not match endpoint ${JSON.stringify(endpoint)}`, details: { issues: [] } } }), { status: 200, headers: { "content-type": "application/json" } }); } try { const result = await handler(endpoint, row?.payload, request.signal); return new Response(serverResponseJson(rpcId, result), { status: 200, headers: { "content-type": "application/json" } }); } catch (error) { log.error?.("netxops: rpc %s failed: %s", endpoint, error instanceof Error ? error.message : String(error)); return new Response(`handler failure: ${String(error)}`, { status: 500 }); } } }; } async function httpBridge(req, res, fetchHandler, maxBodyBytes) { const abort = new AbortController; res.on("close", () => { if (!res.writableEnded) abort.abort(); }); const declaredLen = req.headers["content-length"]; if (declaredLen !== undefined && Number(declaredLen) > maxBodyBytes) { res.writeHead(413, { connection: "close" }); res.end(); req.destroy(); return; } const chunks = []; let received = 0; for await (const chunk of req) { const buffer = chunk; received += buffer.byteLength; if (received > maxBodyBytes) { res.writeHead(413, { connection: "close" }); res.end(); req.destroy(); return; } chunks.push(buffer); } const url = `http://${req.headers.host ?? "127.0.0.1"}${req.url ?? "/"}`; const request = new Request(url, { method: req.method ?? "GET", headers: Object.fromEntries(Object.entries(req.headers).filter(([, value]) => typeof value === "string")), ...chunks.length > 0 ? { body: Buffer.concat(chunks) } : {}, signal: abort.signal }); const response = await fetchHandler.fetch(request); const headers = Object.fromEntries(response.headers.entries()); res.writeHead(response.status, headers); if (response.body === null) { res.end(); return; } for await (const chunk of response.body) { if (!res.write(chunk)) { await new Promise((resolve4) => { const done = () => { res.off("drain", done); res.off("close", done); resolve4(); }; res.once("drain", done); res.once("close", done); }); } } res.end(); } function mountNetxopsWebRoute(ctx, channel, handler) { const webServer = ctx.webServer; if (!webServer || typeof webServer.register !== "function") return null; const connection = ctx.connection; const fetchHandler = netxopsFetchHandler(channel, handler, ctx.logger); const route = { kind: "prefix", path: channel, handler: async (req, res) => { let rejection; if (typeof connection?.requestRejection === "function") { try { rejection = connection.requestRejection(req); } catch { rejection = 403; } } else if (!isTrustedLoopbackRequest(req)) { rejection = 403; } if (rejection !== undefined) { res.writeHead(rejection, { "content-type": "text/plain; charset=utf-8" }); res.end(rejection === 401 ? "unauthorized" : "forbidden"); return; } await httpBridge(req, res, fetchHandler, NETXOPS_RPC_BODY_MAX); } }; const registered = webServer.register(route); if (typeof registered === "function") { return () => { try { registered(); } catch {} }; } if (registered && typeof registered.then === "function") { let done = false; return () => { if (done) return; done = true; registered.then((dispose) => { if (typeof dispose === "function") dispose(); }).catch(() => {}); }; } return () => {}; } // src/netx/tools.ts import { readFileSync as readFileSync8 } from "node:fs"; import { dirname as dirname6, join as join12 } from "node:path"; import { fileURLToPath as fileURLToPath2 } from "node:url"; import { defineTool as defineTool2 } from "@deepseek-ai/dsh-tools"; // src/netx/mcp-catalog.ts function disabledSet(disabledTools) { const out = new Set; for (const name of disabledTools ?? []) { const trimmed = typeof name === "string" ? name.trim() : ""; if (trimmed) out.add(trimmed); } return out; } function parseDisabledTools(value) { if (Array.isArray(value)) { return [...new Set(value.filter((item) => typeof item === "string" && item.trim().length > 0).map((s) => s.trim()))]; } if (typeof value !== "string") return []; const trimmed = value.trim(); if (!trimmed) return []; if (trimmed.startsWith("[")) { try { const parsed = JSON.parse(trimmed); if (Array.isArray(parsed)) { return [...new Set(parsed.filter((item) => typeof item === "string" && item.trim().length > 0).map((s) => s.trim()))]; } } catch {} } return [...new Set(trimmed.split(/[\s,,]+/).filter(Boolean))]; } function filterCatalogTools(catalog, enabledGroups, policy) { const allowGroups = new Set(enabledGroups); const disabled = disabledSet(policy.disabledTools); const readOnlyOnly = policy.readOnlyOnly === true; const out = []; for (const tool2 of catalog) { if (!tool2.group || !allowGroups.has(tool2.group)) continue; if (disabled.has(tool2.name)) continue; if (readOnlyOnly && tool2.readOnlyHint !== true) continue; out.push(tool2); } return out; } function resolveEnabledTools(catalog, options) { if (options.toolsEnabled === false) return []; const enabledGroups = options.forceGroups ?? (options.plane === "preset" ? groupsForPreset({ toolsEnabled: options.toolsEnabled, groups: options.groups }, options.presetId ?? "netxops", options.only) : groupsForPlane(options.groups, options.plane, options.only)); return filterCatalogTools(catalog, enabledGroups, options); } function disabledPublicNames(catalog, enabledGroups, disabledTools, readOnlyOnly) { const allowGroups = new Set(enabledGroups); const disabled = disabledSet(disabledTools); const names = []; for (const tool2 of catalog) { if (!tool2.group || !allowGroups.has(tool2.group)) continue; const off = disabled.has(tool2.name) || readOnlyOnly === true && tool2.readOnlyHint !== true; if (off) names.push(publicToolName(tool2.name)); } return names.sort(); } // src/netx/mcp-remote.ts var PROTOCOL_VERSION = "2025-03-26"; var CLIENT_INFO = { name: "dsh-netxops", version: "0.1.71" }; var SKILLS_URI_PREFIX = "netx://skills/"; class McpRemoteError extends Error { status; constructor(message, status) { super(message); this.name = "McpRemoteError"; this.status = status; } } function mcpEndpointUrl(apiUrl) { const trimmed = apiUrl.trim().replace(/\/$/, ""); if (!trimmed) return ""; return `${trimmed}/mcp`; } function isGroupId(value) { return typeof value === "string" && CAPABILITY_GROUP_IDS.includes(value); } function parseTool(raw) { if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null; const row = raw; const name = typeof row.name === "string" ? row.name.trim() : ""; if (!name) return null; const description = typeof row.description === "string" ? row.description : ""; const inputSchema = row.inputSchema && typeof row.inputSchema === "object" && !Array.isArray(row.inputSchema) ? row.inputSchema : { type: "object", properties: {} }; const annotations = row.annotations && typeof row.annotations === "object" && !Array.isArray(row.annotations) ? row.annotations : {}; const readOnlyHint = annotations.readOnlyHint === true; const meta = row._meta && typeof row._meta === "object" && !Array.isArray(row._meta) ? row._meta : {}; const netx = meta.netx && typeof meta.netx === "object" && !Array.isArray(meta.netx) ? meta.netx : {}; if (netx.group === "meta") return null; const group = isGroupId(netx.group) ? netx.group : undefined; return { name, description, inputSchema, readOnlyHint, group }; } function readMetaNetx(row) { const meta = row._meta ?? row.meta; if (!meta || typeof meta !== "object" || Array.isArray(meta)) return {}; const netx = meta.netx; if (!netx || typeof netx !== "object" || Array.isArray(netx)) return {}; return netx; } function parseSkillResource(resource, text) { if (!resource || typeof resource !== "object" || Array.isArray(resource)) return null; const row = resource; const uri = typeof row.uri === "string" ? row.uri.trim() : ""; const netx = readMetaNetx(row); const group = isGroupId(netx.group) ? netx.group : undefined; const skill = typeof netx.skill === "string" ? netx.skill.trim() : ""; if (!group || !skill) return null; let relPath = typeof row.name === "string" ? row.name.trim().replace(/\\/g, "/") : ""; if (!relPath && uri.startsWith(SKILLS_URI_PREFIX)) { const rest = uri.slice(SKILLS_URI_PREFIX.length); const parts = rest.split("/"); if (parts.length >= 3) relPath = parts.slice(2).join("/"); } if (!relPath || relPath.includes("..")) return null; const unavailableTools = Array.isArray(netx.unavailableTools) ? netx.unavailableTools.filter((n) => typeof n === "string" && n.length > 0) : []; return { group, skill, relPath, text, ...netx.entry === true && unavailableTools.length > 0 ? { unavailableTools } : {} }; } function resourcesCapability(capabilities) { if (!capabilities || typeof capabilities !== "object" || Array.isArray(capabilities)) return false; return "resources" in capabilities; } function parseMcpResponseBody(raw) { const text = raw.replace(/^\uFEFF/, "").trim(); if (!text) throw new McpRemoteError("empty /mcp response body"); if (text.startsWith("{") || text.startsWith("[")) { try { return JSON.parse(text); } catch (error) { throw new McpRemoteError(`mcp JSON parse failed: ${error instanceof Error ? error.message : String(error)}`); } } if (text.includes("data:")) { const dataLines = []; for (const line of text.split(/\r?\n/)) { if (line.startsWith("data:")) dataLines.push(line.slice(5).trim()); } for (let i2 = dataLines.length - 1;i2 >= 0; i2 -= 1) { try { return JSON.parse(dataLines[i2]); } catch {} } } throw new McpRemoteError("mcp response was not JSON"); } function asRpcResult(payload) { if (!payload || typeof payload !== "object" || Array.isArray(payload)) { throw new McpRemoteError("invalid JSON-RPC envelope from /mcp"); } const row = payload; if (row.error && typeof row.error === "object" && !Array.isArray(row.error)) { const err2 = row.error; const message = typeof err2.message === "string" ? err2.message : "JSON-RPC error"; throw new McpRemoteError(message); } if (!("result" in row)) { throw new McpRemoteError("JSON-RPC response missing result"); } return payload.result; } var nextId = 1; async function postRpc(options, method, params, notification = false) { const headers = { "Content-Type": "application/json", Accept: "application/json, text/event-stream" }; const token = options.token.trim(); if (token) headers.Authorization = `Bearer ${token}`; const lang = options.lang?.trim(); if (lang) { headers["Accept-Language"] = lang.toLowerCase().startsWith("en") ? "en" : "zh"; } const body = notification ? { jsonrpc: "2.0", method, ...params ? { params } : {} } : { jsonrpc: "2.0", id: nextId++, method, ...params ? { params } : {} }; const bodyText = JSON.stringify(body); const timeoutMs = typeof options.timeoutMs === "number" && options.timeoutMs > 0 ? options.timeoutMs : 60000; const fetchImpl = options.fetchImpl ?? globalThis.fetch; if (typeof fetchImpl !== "function") { throw new McpRemoteError("fetch is not available"); } const abort = new AbortController; const timer = setTimeout(() => abort.abort(), timeoutMs); const onOuterAbort = () => { abort.abort(); }; options.signal?.addEventListener("abort", onOuterAbort, { once: true }); let response; try { response = await fetchImpl(options.mcpUrl, { method: "POST", headers, body: bodyText, signal: abort.signal }); } catch (error) { if (error instanceof Error && error.name === "AbortError") { throw new McpRemoteError("mcp http timeout"); } throw error; } finally { clearTimeout(timer); options.signal?.removeEventListener("abort", onOuterAbort); } const responseText = await response.text(); if (response.status === 401 || response.status === 403) { throw new McpRemoteError(`HTTP ${response.status} unauthorized`, response.status); } if (response.status < 200 || response.status >= 300) { throw new McpRemoteError(`HTTP ${response.status}${responseText ? `: ${responseText.slice(0, 200)}` : ""}`, response.status); } if (notification) { return; } return asRpcResult(parseMcpResponseBody(responseText)); } async function fetchMcpCatalog(options) { if (!options.mcpUrl) throw new McpRemoteError("mcp url is empty"); const init = await postRpc(options, "initialize", { protocolVersion: PROTOCOL_VERSION, capabilities: {}, clientInfo: CLIENT_INFO }); await postRpc(options, "notifications/initialized", {}, true); const toolsResult = await postRpc(options, "tools/list", {}); const toolsRaw = Array.isArray(toolsResult?.tools) ? toolsResult.tools : []; const tools = []; for (const item of toolsRaw) { const parsed = parseTool(item); if (parsed) tools.push(parsed); } if (!resourcesCapability(init?.capabilities)) { return { tools, skills: [] }; } try { const listed = await postRpc(options, "resources/list", {}); const resources = Array.isArray(listed?.resources) ? listed.resources : []; const skills = []; for (const resource of resources) { if (!resource || typeof resource !== "object" || Array.isArray(resource)) continue; const uri = typeof resource.uri === "string" ? resource.uri.trim() : ""; if (!uri.startsWith(SKILLS_URI_PREFIX)) continue; const read = await postRpc(options, "resources/read", { uri }); const contents = Array.isArray(read?.contents) ? read.contents : []; let text = ""; for (const block of contents) { if (!block || typeof block !== "object" || Array.isArray(block)) continue; const row = block; if (typeof row.text === "string") { text = row.text; break; } } const parsed = parseSkillResource(resource, text); if (parsed) skills.push(parsed); } return { tools, skills }; } catch (error) { const message = error instanceof Error ? error.message : String(error); return { tools, skillsError: message }; } } async function callMcpTool(options, name, args) { if (!options.mcpUrl) throw new McpRemoteError("mcp url is empty"); await postRpc(options, "initialize", { protocolVersion: PROTOCOL_VERSION, capabilities: {}, clientInfo: CLIENT_INFO }); await postRpc(options, "notifications/initialized", {}, true); const result = await postRpc(options, "tools/call", { name, arguments: args }); const content = Array.isArray(result.content) ? result.content : []; return { content, ...result.structuredContent !== undefined ? { structuredContent: result.structuredContent } : {}, ...result.isError === true ? { isError: true } : {} }; } function mcpContentText(content, fallback = "") { const parts = []; for (const block of content) { if (!block || typeof block !== "object" || Array.isArray(block)) continue; const row = block; if (row.type === "text" && typeof row.text === "string") parts.push(row.text); } return parts.join(` `) || fallback; } // src/netx/mcp-schema.ts var ANNOTATIONS = ["description", "title", "default", "examples"]; var SCALAR_TYPES = new Set(["string", "number", "integer", "boolean", "null"]); var CONSTRAINTS = [ "minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum", "multipleOf", "minLength", "maxLength", "pattern", "format", "minItems", "maxItems", "uniqueItems" ]; function isRecord(value) { return typeof value === "object" && value !== null && !Array.isArray(value); } function withAnnotations(source, target) { for (const key of ANNOTATIONS) { if (Object.hasOwn(source, key)) target[key] = source[key]; } const notes = CONSTRAINTS.filter((key) => Object.hasOwn(source, key)).map((key) => `${key}=${JSON.stringify(source[key])}`); if (notes.length > 0) { const base = typeof target.description === "string" ? target.description : ""; target.description = `${base}${base ? " " : ""}(${notes.join(", ")})`; } return target; } function variants(node) { const list = Array.isArray(node.oneOf) ? node.oneOf : Array.isArray(node.anyOf) ? node.anyOf : undefined; if (list) return list.filter(isRecord).filter((branch) => branch.type !== "null"); if (Array.isArray(node.type)) { const { type: _types, ...rest } = node; return node.type.filter((t) => t !== "null").map((t) => ({ ...rest, type: t })); } return; } function toValue(node) { if (!isRecord(node)) return { type: "json" }; const branches = variants(node); if (branches) { if (branches.length === 0) return withAnnotations(node, { type: "json" }); if (branches.length === 1) return withAnnotations(node, toValue({ ...branches[0] })); return withAnnotations(node, { oneOf: branches.map(toValue) }); } const type = node.type; if (type === "object" || type === undefined && isRecord(node.properties)) { const out = { type: "object", additionalProperties: typeof node.additionalProperties === "boolean" ? node.additionalProperties : true }; if (isRecord(node.properties)) out.properties = toProperties(node.properties, node.required); return withAnnotations(node, out); } if (type === "array") { const out = { type: "array" }; if (Object.hasOwn(node, "items")) out.items = toValue(node.items); return withAnnotations(node, out); } if (typeof type === "string" && SCALAR_TYPES.has(type)) { const out = { type }; if (Array.isArray(node.enum) && node.enum.length > 0) out.enum = node.enum; if (Object.hasOwn(node, "const")) out.const = node.const; return withAnnotations(node, out); } return withAnnotations(node, { type: "json" }); } function toProperties(properties, required) { const requiredSet = new Set(Array.isArray(required) ? required.filter((k) => typeof k === "string") : []); const out = {}; for (const [key, value] of Object.entries(properties)) { const converted = toValue(value); if (requiredSet.has(key)) converted.required = true; out[key] = converted; } return out; } function parametersFromInputSchema(schema) { if (!isRecord(schema) || !isRecord(schema.properties)) return {}; return toProperties(schema.properties, schema.required); } // src/netx/mcp-store.ts var STORE_KEY4 = Symbol.for("dsh-netxops.mcp-catalog-store"); var EMPTY = Object.freeze({ phase: "idle", mcpUrl: "", tools: Object.freeze([]), error: "", listedAt: null, fromCache: false }); function store5() { const root = globalThis; let current = root[STORE_KEY4]; if (current === undefined) { current = { state: EMPTY, listeners: new Set, generation: 0 }; root[STORE_KEY4] = current; } return current; } function notify() { for (const listener of [...store5().listeners]) { try { listener(); } catch (error) { console.error("netxops: mcp catalog subscriber failed", error); } } } function publish(next) { store5().state = next; notify(); } function getMcpCatalogState() { return store5().state; } function getMcpCatalogTools() { return store5().state.tools; } function watchMcpCatalog(listener) { const state = store5(); state.listeners.add(listener); return () => { state.listeners.delete(listener); }; } async function refreshMcpCatalog(input) { const mcpUrl = mcpEndpointUrl(input.apiUrl); const state = store5(); const gen = ++state.generation; const previous = state.state; if (!mcpUrl) { const next = { phase: "error", mcpUrl: "", tools: previous.tools, error: "apiUrl empty", listedAt: previous.listedAt, fromCache: previous.tools.length > 0 }; if (gen === store5().generation) publish(next); return next; } publish({ phase: "connecting", mcpUrl, tools: previous.tools, error: "", listedAt: previous.listedAt, fromCache: previous.tools.length > 0 }); try { const fetched = await fetchMcpCatalog({ mcpUrl, token: input.token, lang: input.lang, timeoutMs: input.timeoutMs, signal: input.signal, fetchImpl: input.fetchImpl }); if (gen !== store5().generation) return store5().state; if (fetched.skills !== undefined) { replaceSkillsCache(input.apiUrl, fetched.skills, { token: input.token }); } else { console.warn("netxops: mcp skills pull failed, keeping cache — %s", fetched.skillsError); } const next = { phase: "connected", mcpUrl, tools: Object.freeze([...fetched.tools]), error: "", listedAt: new Date().toISOString(), fromCache: false }; publish(next); return next; } catch (error) { if (gen !== store5().generation) return store5().state; const message = error instanceof McpRemoteError ? error.message : error instanceof Error ? error.message : String(error); const next = { phase: previous.tools.length > 0 ? "offline" : "error", mcpUrl, tools: previous.tools, error: message, listedAt: previous.listedAt, fromCache: previous.tools.length > 0 }; publish(next); return next; } } // src/netx/tools.ts function packageVersion() { try { const here = dirname6(fileURLToPath2(import.meta.url)); const pkg = JSON.parse(readFileSync8(join12(here, "..", "package.json"), "utf8")); return typeof pkg.version === "string" ? pkg.version : "0"; } catch { return "0"; } } function renderMcpResult(_args, value) { if (value && typeof value === "object" && !Array.isArray(value)) { const row = value; if (typeof row.text === "string" && row.text.length > 0) { return [{ type: "text", text: row.text }]; } if (Array.isArray(row.content)) { return [{ type: "text", text: mcpContentText(row.content, JSON.stringify(value)) }]; } } return [{ type: "text", text: typeof value === "string" ? value : JSON.stringify(value ?? null) }]; } var mcpOut = { schema: { type: "object", additionalProperties: true }, render: renderMcpResult }; function defineRemoteTool(tool2, timeoutMs) { const name = publicToolName(tool2.name); return defineTool2({ name, description: tool2.description || name, parameters: parametersFromInputSchema(tool2.inputSchema), output: mcpOut, timeoutMs, isConcurrencySafe: () => tool2.readOnlyHint === true, async execute(args, exec) { const live = getNetxConnection(); const apiUrl = live?.apiUrl ?? ""; const token = live?.token ?? ""; const lang = live?.lang ?? "zh"; const mcpUrl = mcpEndpointUrl(apiUrl); if (!mcpUrl) throw new Error("netx mcp: apiUrl empty"); const result = await callMcpTool({ mcpUrl, token, lang, timeoutMs, signal: exec.signal }, tool2.name, typeof args === "object" && args !== null ? args : {}); const text = mcpContentText(result.content, ""); if (result.isError === true) { throw new Error(text || `Tool "${tool2.name}" failed`); } return JSON.parse(JSON.stringify({ content: result.content, ...result.structuredContent !== undefined ? { structuredContent: result.structuredContent } : {}, ...text ? { text } : {} })); } }); } function registerNetxTools(ctx, connection, options) { const id = getToolMountId(ctx, options.source ?? options.plane); const enabled = resolveEnabledTools(getMcpCatalogTools(), { toolsEnabled: connection.toolsEnabled, groups: connection.groups, disabledTools: connection.disabledTools, readOnlyOnly: connection.readOnlyOnly, plane: options.plane, forceGroups: options.forceGroups, only: options.only, presetId: options.presetId }); const groups = [...new Set(enabled.map((tool2) => tool2.group).filter(Boolean))]; if (enabled.length === 0) { publishToolMountStatus({ id, version: packageVersion(), plane: options.plane, groups, names: [], count: 0, at: new Date().toISOString(), source: options.source ?? options.plane }); return () => { removeToolMountStatus(id); }; } const t = typeof connection.toolCallTimeoutMs === "number" && Number.isFinite(connection.toolCallTimeoutMs) && connection.toolCallTimeoutMs > 0 ? connection.toolCallTimeoutMs : 120000; const disposers = []; const names = []; try { for (const tool2 of enabled) { const definition = defineRemoteTool(tool2, t); disposers.push(ctx.tools.register(definition)); names.push(publicToolName(tool2.name)); } } catch (error) { for (const dispose of disposers.splice(0)) dispose(); publishToolMountStatus({ id, version: packageVersion(), plane: options.plane, groups, names: [], count: 0, at: new Date().toISOString(), error: String(error), source: options.source ?? options.plane }); throw error; } publishToolMountStatus({ id, version: packageVersion(), plane: options.plane, groups, names, count: names.length, at: new Date().toISOString(), source: options.source ?? options.plane }); return () => { for (const dispose of disposers) dispose(); removeToolMountStatus(id); }; } // src/netx/preset-publication.ts import { createScope, scopeOf } from "@deepseek-ai/dsh-scope"; // src/netx/group-tools-plugin.ts import { readFileSync as readFileSync9 } from "node:fs"; import { dirname as dirname7, join as join13 } from "node:path"; import { fileURLToPath as fileURLToPath3 } from "node:url"; function packageVersion2() { try { const here = dirname7(fileURLToPath3(import.meta.url)); const pkg = JSON.parse(readFileSync9(join13(here, "..", "package.json"), "utf8")); return typeof pkg.version === "string" ? pkg.version : "0"; } catch { return "0"; } } function disabledByGroupFor(groupIds, connection) { const catalog = getMcpCatalogTools(); const out = {}; for (const groupId of groupIds) { const names = disabledPublicNames(catalog, [groupId], connection.disabledTools, connection.readOnlyOnly); if (names.length > 0) out[groupId] = names; } return out; } function applyGroupToolsPlugin(ctx, options) { let unregisterTools; let unregisterKbLocal; let unregisterSkills; let skillGeneration = 0; const presetId = () => { if (options.presetId) return options.presetId; const registry = ctx.get("agentPresets"); return registry?.composedPreset?.(ctx) ?? "netxops"; }; const mountId = getToolMountId(ctx, options.name); const resolveGroups = () => { const connection = requireNetxConnection(); return groupsForPreset(connection, presetId(), options.only); }; const remountTools = () => { unregisterTools?.(); unregisterTools = undefined; const connection = requireNetxConnection(); const enabled = resolveGroups(); try { unregisterTools = registerNetxTools(ctx, connection, { plane: "preset", forceGroups: enabled, source: options.name, presetId: presetId() }); ctx.logger.info("%s: groups=[%s] → %s tokenConfigured=%s live=%s catalog=%s", options.name, enabled.join(",") || "(none)", connection.apiUrl, connection.token.trim().length > 0, getNetxConnection() !== undefined, getMcpCatalogTools().length); } catch (error) { publishToolMountStatus({ id: mountId, version: packageVersion2(), plane: "preset", groups: [...enabled], names: [], count: 0, at: new Date().toISOString(), error: String(error), source: options.name }); ctx.logger.error("%s: registerNetxTools failed: %s", options.name, error); } }; const remountKbLocal = () => { unregisterKbLocal?.(); unregisterKbLocal = undefined; const connection = requireNetxConnection(); if (options.only || !kbEnabledForPlane(connection, "preset", presetId())) return; unregisterKbLocal = registerKbLocalTools(ctx, getKbContext(), { plane: "preset", source: `${options.name}:kb-local` }); }; const stopToolWatch = watchNetxConnection(() => { remountTools(); remountKbLocal(); }); const stopCatalogWatch = watchMcpCatalog(() => { remountTools(); }); const stopKbToolWatch = watchKbContext(() => { remountKbLocal(); }); remountTools(); remountKbLocal(); let pollTicks = 0; const pollTimer = setInterval(() => { pollTicks += 1; if (getNetxConnection() !== undefined) { remountTools(); remountKbLocal(); clearInterval(pollTimer); return; } if (pollTicks >= 40) clearInterval(pollTimer); }, 500); ctx.inject(["skills"], (skillsCtx) => { let skillAbort = new AbortController; let packAbort = new AbortController; let unregisterKbSkill; let unregisterKbPack; let packGeneration = 0; const remountSkills = () => { const gen = ++skillGeneration; skillAbort.abort(); skillAbort = new AbortController; unregisterSkills?.(); unregisterSkills = undefined; const connection = requireNetxConnection(); const enabled = resolveGroups(); registerGroupSkills(skillsCtx, enabled, options.name, { signal: skillAbort.signal, disabledByGroup: disabledByGroupFor(enabled, connection), lang: connection.lang }).then((dispose) => { if (gen !== skillGeneration) { dispose(); return; } unregisterSkills = dispose; }).catch((error) => { skillsCtx.logger.warn("%s: skill register failed: %s", options.name, error); }); }; const remountKbSkill = () => { unregisterKbSkill?.(); unregisterKbSkill = undefined; const connection = requireNetxConnection(); if (options.only || !kbEnabledForPlane(connection, "preset", presetId())) return; unregisterKbSkill = registerKbContextSkill(skillsCtx, getKbContext()); }; const remountKbPack = () => { const gen = ++packGeneration; packAbort.abort(); packAbort = new AbortController; unregisterKbPack?.(); unregisterKbPack = undefined; const connection = requireNetxConnection(); const enabled = !options.only && kbEnabledForPlane(connection, "preset", presetId()); registerKbPackSkills(skillsCtx, getKbContext(), { enabled, providerLabel: `${options.name}-kb-pack`, signal: packAbort.signal }).then((dispose) => { if (gen !== packGeneration) { dispose(); return; } unregisterKbPack = dispose; }).catch((error) => { skillsCtx.logger.warn("%s: kb pack skill register failed: %s", options.name, error); }); }; const stopSkillWatch = watchNetxConnection(() => { remountSkills(); remountKbSkill(); remountKbPack(); }); const stopSkillCatalogWatch = watchMcpCatalog(() => { remountSkills(); }); const stopKbWatch = watchKbContext(() => { remountKbSkill(); remountKbPack(); }); remountSkills(); remountKbSkill(); remountKbPack(); skillsCtx.effect(() => () => { skillGeneration += 1; packGeneration += 1; skillAbort.abort(); packAbort.abort(); stopSkillWatch(); stopSkillCatalogWatch(); stopKbWatch(); unregisterSkills?.(); unregisterSkills = undefined; unregisterKbSkill?.(); unregisterKbSkill = undefined; unregisterKbPack?.(); unregisterKbPack = undefined; }, `${options.name}: dispose skills`); }); ctx.effect(() => () => { clearInterval(pollTimer); stopToolWatch(); stopCatalogWatch(); stopKbToolWatch(); unregisterTools?.(); unregisterTools = undefined; unregisterKbLocal?.(); unregisterKbLocal = undefined; removeToolMountStatus(mountId); }, `${options.name}: dispose tools`); } // src/netx/preset-publication.ts function installPresetPublication(ctx) { ctx.inject(["agents", "agentPresets", "tools"], (child) => { const agents = child.get("agents"); const presets = child.get("agentPresets"); const mounts = new Map; let active = true; const release = (id) => { const mount = mounts.get(id); if (!mount) return; mounts.delete(id); mount.scope.rawDispose(); }; const sync = (agent) => { if (!active) return; const presetId = presets.composedPreset(agent.ctx); const connection = getNetxConnection(); const enabled = connection && presetId && presetId !== "netxops" && (groupsForPreset(connection, presetId).length > 0 || kbEnabledForPlane(connection, "preset", presetId)); const previous = mounts.get(agent.id); if (!enabled || previous?.presetId !== presetId) release(agent.id); if (!enabled || mounts.has(agent.id)) return; const key = scopeOf(agent.ctx); if (!key) { child.logger.warn("netxops: selected preset %s has no session scope", presetId); return; } const scope = createScope(child, key); mounts.set(agent.id, { presetId, scope }); applyGroupToolsPlugin(scope.ctx, { name: `netxops-selected:${presetId}:${agent.id}`, mode: "settings", presetId }); }; const syncAll = () => { for (const agent of agents.list()) sync(agent); }; child.on("agent/created", ({ agent }) => { sync(agent); }); child.on("agent/disposed", ({ agent }) => { release(agent.id); }); child.on("agent-preset/selected", () => { syncAll(); }); const stop = watchNetxConnection(syncAll); syncAll(); child.effect(() => () => { active = false; stop(); for (const id of [...mounts.keys()]) release(id); }, "netxops: selected preset publication"); }); } // src/index.ts function mcpCatalogPayload(catalog) { const connection = getNetxConnection(); const cached = listCachedSkills(connection?.apiUrl ?? "", connection?.token ?? ""); return { phase: catalog.phase, mcpUrl: catalog.mcpUrl, error: catalog.error, listedAt: catalog.listedAt, fromCache: catalog.fromCache, tools: catalog.tools.map((tool2) => ({ name: tool2.name, description: tool2.description, readOnlyHint: tool2.readOnlyHint, group: tool2.group ?? null })), skills: cached.skills, skillsListedAt: cached.listedAt }; } var name = "netxops"; var inject = ["tools"]; var NETXOPS_RPC_CHANNEL = "/netxops"; var NETXOPS_SETTINGS_NAMESPACE = "netxops"; var DEFAULT_TOKEN_REF = "NETX_API_TOKEN"; var DEFAULT_TOOL_CALL_TIMEOUT_MS = 120000; var DEFAULT_API_URL = "http://127.0.0.1:8890"; var DEFAULT_LANG = "zh"; function vol(schema) { const candidate = schema; return typeof candidate.volatile === "function" ? candidate.volatile() : schema; } var Config = z.object({ toolsEnabled: vol(z.boolean().default(true)), groupOpsPresets: vol(z.string().default("")), groupTopologyPresets: vol(z.string().default("")), groupBizMonitorPresets: vol(z.string().default("")), groupKbPresets: vol(z.string().default("")), apiUrl: vol(z.string().default("http://127.0.0.1:8890")), lang: vol(z.string().default("zh")), thinkingLanguage: vol(z.string().default("auto")), replyLanguage: vol(z.string().default("follow-user")), tokenCredentialRef: vol(z.string().role("credential-ref").default(DEFAULT_TOKEN_REF)), toolCallTimeoutMs: vol(z.number().step(1).min(1000).default(120000)), installAgentPreset: vol(z.boolean().default(true)), alarmPushEnabled: vol(z.boolean().default(false)), alarmDeliverDsh: vol(z.boolean().default(true)), alarmDeliverIm: vol(z.boolean().default(false)), imTargets: vol(z.string().default("")), imBotId: vol(z.string().default("")), imTargetId: vol(z.string().default("")), nmsProvider: vol(z.string().default("zte-ume")), groupOpsInPreset: vol(z.boolean().default(true)), groupOpsPublic: vol(z.boolean().default(false)), groupTopologyInPreset: vol(z.boolean().default(false)), groupTopologyPublic: vol(z.boolean().default(false)), groupBizMonitorInPreset: vol(z.boolean().default(true)), groupBizMonitorPublic: vol(z.boolean().default(false)), kbRoot: vol(z.string().default("")), groupKbInPreset: vol(z.boolean().default(true)), groupKbPublic: vol(z.boolean().default(false)), disabledTools: vol(z.string().default("[]")), readOnlyOnly: vol(z.boolean().default(false)) }); function mergeConfigSkippingNullish(base, patch) { const live = Object.fromEntries(Object.entries(readConfigSnapshot(patch)).filter(([, v]) => v !== null && v !== undefined)); return { ...readConfigSnapshot(base), ...live }; } function resolveToolCallTimeoutMs(value, fallback = DEFAULT_TOOL_CALL_TIMEOUT_MS) { value = readConfigValue(value); return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback; } function resolveLang(value, fallback = DEFAULT_LANG) { value = readConfigValue(value); return typeof value === "string" && value.trim().length > 0 ? value.trim() : fallback; } function resolveApiUrlForConnection(value) { value = readConfigValue(value); const raw = typeof value === "string" ? value.trim() : ""; if (!raw) { const normalized2 = normalizeApiUrl(DEFAULT_API_URL); return { url: normalized2.ok ? normalized2.url : DEFAULT_API_URL, usedDefault: true }; } const normalized = normalizeApiUrl(raw); if (normalized.ok) return { url: normalized.url, usedDefault: false }; return { url: "", usedDefault: false, error: normalized.error }; } function packageVersion3() { try { const here = dirname8(fileURLToPath4(import.meta.url)); const pkg = JSON.parse(readFileSync10(join14(here, "..", "package.json"), "utf8")); return typeof pkg.version === "string" ? pkg.version : "0"; } catch { return "0"; } } function resolveTokenCredentialRef(value) { value = readConfigValue(value); return typeof value === "string" && /^[A-Za-z_][A-Za-z0-9_]*$/.test(value.trim()) ? value.trim() : DEFAULT_TOKEN_REF; } function credentialsFrom(ctx) { const direct = ctx.credentials; if (direct && typeof direct.resolve === "function") return direct; if (typeof ctx.get === "function") { const viaGet = ctx.get("credentials"); if (viaGet && typeof viaGet.resolve === "function") return viaGet; } return; } async function resolveToken(ctx, refName, log) { const ref = resolveTokenCredentialRef(refName); const fromEnv = () => process.env[ref]?.trim() ?? ""; try { const credentials = credentialsFrom(ctx); if (!credentials?.resolve) { const env2 = fromEnv(); if (!env2) log?.warn("netxops: credentials service unavailable; %s empty", ref); return env2; } const hit = await credentials.resolve(credentialRef(ref)); const value = typeof hit?.value === "string" ? hit.value.trim() : ""; if (value) return value; const env = fromEnv(); if (!env) log?.warn("netxops: credential %s resolve returned empty", ref); return env; } catch (error) { log?.warn("netxops: credential %s resolve failed: %s", ref, error); return fromEnv(); } } function describeRows(describe) { if (typeof describe !== "function") return []; try { const raw = describe(); if (Array.isArray(raw)) return raw; if (raw && typeof raw === "object" && Array.isArray(raw.namespaces)) { return raw.namespaces; } } catch {} return []; } function installNetxopsSettings(ctx, entry, hooks) { const legacy = DshSettings.installSettingsSection; if (typeof legacy === "function") { legacy(ctx, NETXOPS_SETTINGS_NAMESPACE, Config, readConfigSnapshot(entry), hooks); return; } ctx.inject(["settings"], (settingsCtx) => { const settings = settingsCtx.settings; if (typeof settings.installSection === "function") { settings.installSection(ctx, NETXOPS_SETTINGS_NAMESPACE, Config, readConfigSnapshot(entry), hooks); return; } const readLive = () => { const row = describeRows(settings.describe).find((item) => item.ns === NETXOPS_SETTINGS_NAMESPACE); if (row?.value !== null && typeof row?.value === "object" && !Array.isArray(row.value)) { return mergeConfigSkippingNullish(entry, row.value); } return readConfigSnapshot(entry); }; hooks.setSource(readLive); hooks.onChange(); let lastFingerprint = JSON.stringify(readLive()); const bumpIfChanged = (reason) => { const next = readLive(); const fingerprint = JSON.stringify(next); if (fingerprint === lastFingerprint) return; lastFingerprint = fingerprint; settingsCtx.logger.info("netxops: live settings changed (%s) → republish connection", reason); hooks.onChange(); }; settingsCtx.effect(() => { const onDocumentUpdated = (ns) => { if (ns !== NETXOPS_SETTINGS_NAMESPACE && ns !== "dsh-netxops") return; bumpIfChanged("document-updated"); }; const off = settingsCtx.on("settings/document-updated", onDocumentUpdated); const timer = setInterval(() => { bumpIfChanged("poll"); }, 2000); return () => { clearInterval(timer); if (typeof off === "function") off(); }; }, "netxops: settings live follow"); settingsCtx.logger.info("netxops: hot-following settings (document-updated + describe poll)"); }); } function apply(ctx, config = Config({})) { let source = () => readConfigSnapshot(config); const initial = source(); let generation = 0; let disposed = false; let resolvedTokenRef = resolveTokenCredentialRef(initial.tokenCredentialRef); let stopAlarmPush; let credentialsCtx = ctx; if (initial.installAgentPreset) { const profile = resolveActiveProfile(ctx.baseUrl); let scheduled = false; const fallback = setTimeout(() => { if (!scheduled && !disposed && source().installAgentPreset) { ensureAgentPresetInstalled(ctx.logger, profile); } }, 1000); ctx.inject(["agentPresets"], (presetCtx) => { scheduled = true; clearTimeout(fallback); let active = true; const timer = setTimeout(() => { (async () => { const registry = presetCtx.get("agentPresets"); let document; try { document = (await registry.readDocument?.("standard"))?.content; } catch (error) { presetCtx.logger.warn("netxops: cannot read Host standard preset: %s", error); } if (active && !disposed && source().installAgentPreset) { ensureAgentPresetInstalled(presetCtx.logger, profile, document); } })().catch((error) => { presetCtx.logger.warn("netxops: preset installation failed: %s", error); }); }, 0); presetCtx.effect(() => () => { active = false; clearTimeout(timer); }, "netxops: Host standard composition"); }); ctx.effect(() => () => { clearTimeout(fallback); }, "netxops: preset installation fallback"); } const connectionSnapshot = (current, token) => { const resolved = resolveApiUrlForConnection(current.apiUrl); if (resolved.error) { ctx.logger.warn("netxops: invalid apiUrl (%s) — using empty url until fixed; use http(s) without embedded credentials", resolved.error); } else if (resolved.usedDefault) { ctx.logger.warn("netxops: apiUrl missing/null in settings — falling back to %s", resolved.url); } return { toolsEnabled: current.toolsEnabled !== false, groupKbPresets: current.groupKbPresets ?? "", apiUrl: resolved.url, token, lang: resolveLang(current.lang), toolCallTimeoutMs: resolveToolCallTimeoutMs(current.toolCallTimeoutMs), groups: capabilityGroupsFromSettings(current), disabledTools: parseDisabledTools(current.disabledTools), readOnlyOnly: current.readOnlyOnly === true, groupKbInPreset: typeof current.groupKbInPreset === "boolean" ? current.groupKbInPreset : true, groupKbPublic: current.groupKbPublic === true }; }; const provisional = connectionSnapshot(initial, ""); publishNetxConnection(provisional); installPresetPublication(ctx); ctx.logger.info("netxops: provisional connection published → %s (token pending credentials)", provisional.apiUrl || "(invalid apiUrl)"); const toolsApi = (() => { try { const fromGet = typeof ctx.get === "function" ? ctx.get("tools") : undefined; if (fromGet && typeof fromGet.register === "function") { return fromGet; } } catch {} const direct = ctx.tools; return direct && typeof direct.register === "function" ? direct : undefined; })(); if (!toolsApi) { ctx.logger.warn("netxops: tools missing on apply — host tool mount skipped (loader should inject tools)"); publishToolMountStatus({ version: "unknown", plane: "host", groups: [], names: [], count: 0, at: new Date().toISOString(), error: "tools service missing on host apply", source: "host" }); } else { let disposeToolsMount; let disposeKbLocal; const remountHostTools = () => { if (disposed) return; disposeToolsMount?.(); disposeToolsMount = undefined; const connection = getNetxConnection(); if (!connection) return; const enabled = connection.toolsEnabled === false ? [] : groupsForPlane(connection.groups, "host"); disposeToolsMount = ctx.effect(() => { try { const unregister = registerNetxTools(ctx, connection, { plane: "host" }); ctx.logger.info("netxops: host tools groups=[%s] count=%s", enabled.join(",") || "(none)", getToolMountStatus()?.count ?? 0); return () => { unregister(); }; } catch (error) { publishToolMountStatus({ version: packageVersion3(), plane: "host", groups: [...enabled], names: [], count: 0, at: new Date().toISOString(), error: String(error) }); ctx.logger.error("netxops: host tools register failed: %s", error); return () => {}; } }, "netxops: host tools mount"); }; const remountKbLocal = () => { if (disposed) return; disposeKbLocal?.(); disposeKbLocal = undefined; const connection = getNetxConnection(); if (!connection) return; if (!kbEnabledForPlane(connection, "host")) return; disposeKbLocal = ctx.effect(() => { const unregister = registerKbLocalTools(ctx, getKbContext(), { plane: "host", source: "netxops-host:kb-local" }); return () => { unregister?.(); }; }, "netxops: host kb-local tools"); }; const stopWatch = watchNetxConnection(() => { remountHostTools(); remountKbLocal(); }); const stopCatalogWatch = watchMcpCatalog(() => { remountHostTools(); }); const stopKbWatch = watchKbContext(() => { remountKbLocal(); }); remountHostTools(); remountKbLocal(); ctx.effect(() => () => { stopWatch(); stopCatalogWatch(); stopKbWatch(); disposeToolsMount?.(); disposeKbLocal?.(); resetToolMountStatus(); }, "netxops: dispose host tools"); } const restartAlarmPush = (current, apiUrl, token) => { stopAlarmPush?.(); stopAlarmPush = undefined; resetAlarmSession(); if (!current.alarmPushEnabled) { resetAlarmPushStatus(); return; } if (!token.trim()) { ctx.logger.warn("netxops alarm-push: enabled but token is empty — not connecting"); publishAlarmPushStatus({ phase: "error", enabled: true, wsUrl: "", detail: "missing_token", lastError: "token empty" }); return; } const lang = resolveLang(current.lang); const deliverDsh = current.alarmDeliverDsh !== false; const imTargets = resolveImTargets({ imTargets: current.imTargets ?? "", imBotId: current.imBotId ?? "", imTargetId: current.imTargetId ?? "" }); const deliverIm = imTargets.length > 0; stopAlarmPush = startAlarmPushClient({ apiUrl, token, logger: ctx.logger, onAlarm: async (payload) => { await dispatchAlarmToSinks(ctx, payload, { deliverDsh, deliverIm, imTargets, lang }); } }); }; const publish2 = () => { if (disposed) return; const gen = ++generation; const currentSync = source(); const previous = getNetxConnection(); const sameCredential = resolveTokenCredentialRef(currentSync.tokenCredentialRef) === resolvedTokenRef; const sameHost = resolveApiUrlForConnection(currentSync.apiUrl).url === previous?.apiUrl; const prevToken = sameCredential && sameHost ? previous?.token ?? "" : ""; publishNetxConnection(connectionSnapshot(currentSync, prevToken)); const kb = resolveKbRoot(currentSync.kbRoot ?? ""); publishKbContext(kb); applyKbEnv(kb); if (!currentSync.alarmPushEnabled) { stopAlarmPush?.(); stopAlarmPush = undefined; resetAlarmPushStatus(); } (async () => { const current = source(); const tokenRef = resolveTokenCredentialRef(current.tokenCredentialRef); const token = await resolveToken(credentialsCtx, tokenRef, ctx.logger); if (disposed || gen !== generation) return; resolvedTokenRef = tokenRef; if (current.nmsProvider && current.nmsProvider !== "zte-ume") { ctx.logger.warn("netxops: nmsProvider=%s is not implemented yet; using zte-ume REST adapter", current.nmsProvider); } const snapshot = connectionSnapshot(current, token); publishNetxConnection(snapshot); const kb2 = resolveKbRoot(current.kbRoot ?? ""); publishKbContext(kb2); applyKbEnv(kb2); if (kb2.status === "configured") { ctx.logger.info("netxops: knowledge base configured → %s (%s / %s v%s)", kb2.realRoot, kb2.operatorName, kb2.country, kb2.version); } else if (kb2.status === "error") { ctx.logger.warn("netxops: knowledge base error — %s", kb2.errorMessage); } restartAlarmPush(current, snapshot.apiUrl, token); const tokenConfigured = token.trim().length > 0; const catalog = await refreshMcpCatalog({ apiUrl: snapshot.apiUrl, token, lang: snapshot.lang, timeoutMs: Math.min(snapshot.toolCallTimeoutMs, 45000) }); if (disposed || gen !== generation) return; if (catalog.phase === "connected") { ctx.logger.info("netxops: mcp catalog ok → %s tools=%s", catalog.mcpUrl, catalog.tools.length); } else if (catalog.error) { ctx.logger.warn("netxops: mcp catalog %s → %s (%s)", catalog.phase, catalog.mcpUrl || "(no url)", catalog.error); } if (!tokenConfigured) { ctx.logger.warn("netxops: published connection → %s tokenConfigured=false (set credential %s)", snapshot.apiUrl, tokenRef); } else { const imSinkCount = resolveImTargets({ imTargets: current.imTargets ?? "", imBotId: current.imBotId ?? "", imTargetId: current.imTargetId ?? "" }).length; ctx.logger.info("netxops: published connection → %s tokenConfigured=true alarmPush=%s dsh=%s im=%s host=[%s]", snapshot.apiUrl, current.alarmPushEnabled === true, current.alarmDeliverDsh !== false, imSinkCount, groupsForPlane(snapshot.groups, "host").join(",") || "(none)"); } })().catch((error) => { ctx.logger.error("netxops: connection publish error: %s", error); }); }; publish2(); ctx.on("loader/volatile-update", () => { publish2(); }); installNetxopsSettings(ctx, config, { setSource: (current) => { source = () => readConfigSnapshot(current()); }, onChange: () => { publish2(); } }); ctx.inject(["credentials"], (credCtx) => { credentialsCtx = credCtx; publish2(); credCtx.on("credentials/reference-updated", (ref) => { if (String(ref) === resolveTokenCredentialRef(source().tokenCredentialRef)) publish2(); }); }); ctx.inject(["systemPrompt", "settings"], (promptCtx) => { const systemPrompt = promptCtx.systemPrompt; const liveConfig = () => { const settings = promptCtx.get("settings"); const section = typeof settings?.get === "function" ? (() => { try { return settings.get(NETXOPS_SETTINGS_NAMESPACE); } catch { return; } })() : undefined; if (section !== null && typeof section === "object" && !Array.isArray(section)) { return mergeConfigSkippingNullish(source(), section); } return source(); }; const effectiveThinking = () => { const settings = promptCtx.get("settings"); const current = liveConfig(); return resolveThinkingLanguage(normalizeThinkingLanguage(current.thinkingLanguage), readSystemLocalePreference(settings)); }; const replySetting = () => normalizeReplyLanguage(liveConfig().replyLanguage); promptCtx.effect(() => { const disposers = []; const push = (dispose) => { if (typeof dispose === "function") disposers.push(dispose); }; push(systemPrompt.section({ name: "netxops:thinking-language", order: 85, text: () => thinkingInstruction(effectiveThinking()) })); push(systemPrompt.context({ name: "netxops:thinking-language-reminder", order: 1000, text: () => thinkingReminder(effectiveThinking()) })); push(systemPrompt.section({ name: "netxops:reply-language", order: 10150, text: () => replyInstruction(replySetting()) })); push(systemPrompt.context({ name: "netxops:reply-language-reminder", order: 1100, text: () => replyReminder(replySetting()) })); promptCtx.logger.info("netxops: model language prompts registered (thinking=%s reply=%s)", normalizeThinkingLanguage(liveConfig().thinkingLanguage), replySetting()); return () => { for (const dispose of disposers.splice(0)) dispose(); }; }, "netxops: model language prompts"); }); ctx.inject(["skills"], (skillsCtx) => { let skillAbort = new AbortController; let packAbort = new AbortController; let unregisterSkills; let unregisterKbSkill; let unregisterKbPack; let generation2 = 0; let packGeneration = 0; const remountHostSkills = () => { if (disposed) return; const gen = ++generation2; skillAbort.abort(); skillAbort = new AbortController; unregisterSkills?.(); unregisterSkills = undefined; const connection = getNetxConnection(); if (!connection) return; const enabled = connection.toolsEnabled === false ? [] : groupsForPlane(connection.groups, "host"); const disabledByGroup = Object.fromEntries(enabled.map((groupId) => [ groupId, disabledPublicNames(getMcpCatalogTools(), [groupId], connection.disabledTools, connection.readOnlyOnly) ])); registerGroupSkills(skillsCtx, enabled, "netxops-host", { signal: skillAbort.signal, disabledByGroup, lang: connection.lang }).then((dispose) => { if (gen !== generation2) { dispose(); return; } unregisterSkills = dispose; skillsCtx.logger.info("netxops: host skills groups=[%s]", enabled.join(",") || "(none)"); }).catch((error) => { skillsCtx.logger.warn("netxops: host skill register failed: %s", error); }); }; const remountKbSkill = () => { if (disposed) return; unregisterKbSkill?.(); unregisterKbSkill = undefined; const connection = getNetxConnection(); if (!kbEnabledForPlane(connection, "host")) return; const snapshot = getKbContext(); unregisterKbSkill = registerKbContextSkill(skillsCtx, snapshot); skillsCtx.logger.info("netxops: kb-context skill status=%s (host/public)", snapshot.status); }; const remountKbPack = () => { if (disposed) return; const gen = ++packGeneration; packAbort.abort(); packAbort = new AbortController; unregisterKbPack?.(); unregisterKbPack = undefined; const connection = getNetxConnection(); const enabled = kbEnabledForPlane(connection, "host") && Boolean(connection); registerKbPackSkills(skillsCtx, getKbContext(), { enabled, providerLabel: "netxops-kb-pack-host", signal: packAbort.signal }).then((dispose) => { if (gen !== packGeneration) { dispose(); return; } unregisterKbPack = dispose; }).catch((error) => { skillsCtx.logger.warn("netxops: host kb pack skill register failed: %s", error); }); }; const stopWatch = watchNetxConnection(() => { remountHostSkills(); remountKbSkill(); remountKbPack(); }); const stopCatalogWatch = watchMcpCatalog(() => { remountHostSkills(); }); const stopKbWatch = watchKbContext(() => { remountKbSkill(); remountKbPack(); }); remountHostSkills(); remountKbSkill(); remountKbPack(); skillsCtx.effect(() => () => { generation2 += 1; packGeneration += 1; skillAbort.abort(); packAbort.abort(); stopWatch(); stopCatalogWatch(); stopKbWatch(); unregisterSkills?.(); unregisterKbSkill?.(); unregisterKbPack?.(); }, "netxops: dispose host skills"); }); const handleNetxopsRpc = async (endpoint, payload) => { if (endpoint === "presets.catalog") { const registry = ctx.get("agentPresets"); if (!registry?.list) return { ok: false, error: "preset_registry_unavailable" }; const presets = await registry.list(); return { ok: true, value: { presets: presets.map(({ id, name: name2, broken }) => ({ id, name: name2, broken })) } }; } if (endpoint === "tools.status") { const mount = getToolMountStatus(); const connection = getNetxConnection(); const catalog = getMcpCatalogState(); return { ok: true, value: { mount: mount ?? null, mounts: getToolMountStatuses(), mcp: { phase: catalog.phase, mcpUrl: catalog.mcpUrl, error: catalog.error, listedAt: catalog.listedAt, fromCache: catalog.fromCache, toolCount: catalog.tools.length }, connection: connection ? { apiUrl: connection.apiUrl, toolsEnabled: connection.toolsEnabled !== false, tokenConfigured: connection.token.trim().length > 0, hostGroups: connection.toolsEnabled === false ? [] : groupsForPlane(connection.groups, "host"), presetGroups: connection.toolsEnabled === false ? [] : groupsForPlane(connection.groups, "preset"), publicGroups: connection.toolsEnabled === false ? [] : groupsForPlane(connection.groups, "public"), disabledTools: [...connection.disabledTools ?? []], readOnlyOnly: connection.readOnlyOnly === true } : null } }; } if (endpoint === "tools.catalog") { return { ok: true, value: mcpCatalogPayload(getMcpCatalogState()) }; } if (endpoint === "tools.refresh") { const connection = getNetxConnection(); if (!connection) return { ok: false, error: "connection_unavailable" }; const catalog = await refreshMcpCatalog({ apiUrl: connection.apiUrl, token: connection.token, lang: connection.lang, timeoutMs: Math.min(connection.toolCallTimeoutMs, 45000) }); return { ok: catalog.phase === "connected", value: { ...mcpCatalogPayload(catalog), toolCount: catalog.tools.length }, ...catalog.error ? { error: catalog.error } : {} }; } if (endpoint === "alarm-push.status") { return { ok: true, value: getAlarmPushStatus() }; } if (endpoint === "kb.status") { return { ok: true, value: getKbContext() }; } if (endpoint === "kb.reload") { const current = source(); const kb = resolveKbRoot(current.kbRoot ?? ""); publishKbContext(kb, { force: true }); applyKbEnv(kb); if (kb.status === "configured") { ctx.logger.info("netxops: kb.reload → %s (%s / %s v%s)", kb.realRoot, kb.operatorName, kb.country, kb.version); } else if (kb.status === "error") { ctx.logger.warn("netxops: kb.reload error — %s", kb.errorMessage); } else { ctx.logger.info("netxops: kb.reload → unconfigured (kbRoot empty)"); } return { ok: true, value: kb }; } if (endpoint === "kb.resolve") { const path = extractKbResolvePath(payload); return { ok: true, value: resolveKbRoot(path) }; } if (endpoint === "sessions.export.status") { const value = await getSessionsExportStatus(ctx); return { ok: true, value }; } if (endpoint === "im-delivery.catalog") { const fromGet = typeof ctx.get === "function" ? ctx.get("dshIm") : undefined; const im = fromGet ?? ctx.dshIm; if (!im || typeof im.listDeliveryCatalog !== "function") { return { ok: true, value: { available: false, options: [], reasonCode: "im_catalog_unavailable", hint: "dsh-im-ops missing or outdated — install ≥ops.24 for delivery picker" } }; } try { const options = await im.listDeliveryCatalog(); return { ok: true, value: { available: true, options: Array.isArray(options) ? options : [] } }; } catch (error) { return { ok: true, value: { available: false, options: [], hint: error instanceof Error ? error.message : String(error) } }; } } return { ok: false, error: { code: "bad-request", message: "Unknown endpoint." } }; }; ctx.inject(["webServer", "connection"], (mountCtx) => { mountCtx.effect(() => { const direct = mountNetxopsWebRoute(mountCtx, NETXOPS_RPC_CHANNEL, (endpoint, payload) => handleNetxopsRpc(endpoint, payload)); if (direct !== null) { mountCtx.logger.info("netxops: /netxops rpc mounted on webServer"); return direct; } const rpc = mountCtx.get("connection")?.rpc; if (rpc && typeof rpc.handle === "function") { mountCtx.logger.info("netxops: /netxops rpc mounted via connection.rpc.handle (legacy)"); const legacy = rpc.handle(NETXOPS_RPC_CHANNEL, (endpoint, payload) => handleNetxopsRpc(endpoint, payload)); return () => { if (typeof legacy === "function") legacy(); }; } mountCtx.logger.warn("netxops: /netxops rpc unavailable — settings card status disabled"); return () => {}; }, "netxops: /netxops web rpc"); }); ctx.inject(["connection"], (connCtx) => { const connection = connCtx.get("connection"); const fetchApi = connection?.fetch; if (!fetchApi || typeof fetchApi.register !== "function") { connCtx.logger.warn("netxops: connection.fetch.register unavailable — sessions export download disabled"); } else { connCtx.effect(() => { const dispose = fetchApi.register({ path: NETXOPS_SESSIONS_EXPORT_PATH, methods: ["GET", "HEAD"], fetch: async (request) => { if (request.method === "HEAD") { return sessionsExportHeadResponse(ctx, request); } return sessionsExportResponse(ctx, request); } }); return () => { dispose(); }; }, "netxops: sessions export fetch"); } }); ctx.effect(() => () => { disposed = true; generation += 1; publishNetxConnection({ ...provisional, token: "", toolsEnabled: false }); stopAlarmPush?.(); stopAlarmPush = undefined; resetAlarmSession(); resetAlarmPushStatus(); resetKbContext(); }, "netxops: dispose host bridge"); } function extractKbResolvePath(payload) { if (payload === null || typeof payload !== "object" || Array.isArray(payload)) return ""; const row = payload; if (typeof row.path === "string") return row.path; const args = row.args; if (args !== null && typeof args === "object" && !Array.isArray(args)) { const path = args.path; if (typeof path === "string") return path; } return ""; } export { writePatchIfUnchanged, syncDeclarativeIntoProfilePatch, resolveToolCallTimeoutMs, resolveTokenCredentialRef, resolveLang, resolveApiUrlForConnection, normalizeApiUrl, name, mergeHostConfigForHeal, mergeConfigSkippingNullish, installDirectoryPreset, inject, ensureAgentPresetInstalled, composeNetxopsPresetArtifacts, apply, NETXOPS_SETTINGS_NAMESPACE, NETXOPS_RPC_CHANNEL, NETXOPS_PRESET_ID, NETXOPS_HOST_CONFIG_DEFAULTS, DEFAULT_TOOL_CALL_TIMEOUT_MS, DEFAULT_TOKEN_REF, DEFAULT_LANG, DEFAULT_API_URL, Config };