mirror of
https://github.com/hansjone/netxops.git
synced 2026-10-16 06:50:46 +08:00
Heal sparse Desktop overrides and default-publish ops tools (0.1.51).
Plugin Manager often leaves a bare netxops enable stub that wipes Config on DSH 0.2; expand missing keys on Host apply, default groupOpsPublic on, and persist false explicitly so other installs get tools without hand-editing patches. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
d4f2459eb2
commit
6c9f323368
18 changed files with 593 additions and 167 deletions
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
# Host-plane Netx Ops bridge: settings + credentials → connection publish.
|
# Host-plane Netx Ops bridge: settings + credentials → connection publish.
|
||||||
# Ops-scoped `netx__*` tools mount from the agent preset (`dsh-netxops/tools`).
|
# Ops-scoped `netx__*` tools mount from the agent preset (`dsh-netxops/tools`).
|
||||||
# Capability groups: ops in preset; topology / public off.
|
# Capability groups: ops in preset + public on; topology off.
|
||||||
# Rule: one group ↔ one skill (netx-ops / netx-topology).
|
# Rule: one group ↔ one skill (netx-ops / netx-topology).
|
||||||
#
|
#
|
||||||
# IM + cron are separate profile bundles (pnpm blockExoticSubdeps forbids
|
# IM + cron are separate profile bundles (pnpm blockExoticSubdeps forbids
|
||||||
|
|
@ -34,7 +34,7 @@
|
||||||
imTargetId: ''
|
imTargetId: ''
|
||||||
imTargets: ''
|
imTargets: ''
|
||||||
groupOpsInPreset: true
|
groupOpsInPreset: true
|
||||||
groupOpsPublic: false
|
groupOpsPublic: true
|
||||||
groupTopologyInPreset: false
|
groupTopologyInPreset: false
|
||||||
groupTopologyPublic: false
|
groupTopologyPublic: false
|
||||||
groupBizMonitorInPreset: true
|
groupBizMonitorInPreset: true
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Host-plane Netx Ops bridge: settings + credentials → connection publish.
|
# Host-plane Netx Ops bridge: settings + credentials → connection publish.
|
||||||
# Ops-scoped `netx__*` tools mount from the agent preset (`dsh-netxops/tools`).
|
# Ops-scoped `netx__*` tools mount from the agent preset (`dsh-netxops/tools`).
|
||||||
# Capability groups: ops in preset; topology / public off.
|
# Capability groups: ops in preset + public on; topology off.
|
||||||
# Rule: one group ↔ one skill (netx-ops / netx-topology).
|
# Rule: one group ↔ one skill (netx-ops / netx-topology).
|
||||||
#
|
#
|
||||||
# IM + cron are separate profile bundles (pnpm blockExoticSubdeps forbids
|
# IM + cron are separate profile bundles (pnpm blockExoticSubdeps forbids
|
||||||
|
|
@ -30,7 +30,7 @@
|
||||||
imTargetId: ''
|
imTargetId: ''
|
||||||
imTargets: ''
|
imTargets: ''
|
||||||
groupOpsInPreset: true
|
groupOpsInPreset: true
|
||||||
groupOpsPublic: false
|
groupOpsPublic: true
|
||||||
groupTopologyInPreset: false
|
groupTopologyInPreset: false
|
||||||
groupTopologyPublic: false
|
groupTopologyPublic: false
|
||||||
groupBizMonitorInPreset: true
|
groupBizMonitorInPreset: true
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ One command, three **direct** profile bundles. Do **not** nest IM/cron under net
|
||||||
Token fallback: `scripts/set-netx-token.ps1` / `.sh`.
|
Token fallback: `scripts/set-netx-token.ps1` / `.sh`.
|
||||||
Optional: enable **关键告警推送** so this DSH dials `ws(s)://<apiUrl>/v1/integrations/dsh-alarm/ws`. Choose **投递到 DSH 会话** and/or **投递到 WhatsApp / IM**. For IM, install `dsh-im-ops`, create a delivery target, then paste `imBotId` / `imTargetId`. The card header shows live WSS status.
|
Optional: enable **关键告警推送** so this DSH dials `ws(s)://<apiUrl>/v1/integrations/dsh-alarm/ws`. Choose **投递到 DSH 会话** and/or **投递到 WhatsApp / IM**. For IM, install `dsh-im-ops`, create a delivery target, then paste `imBotId` / `imTargetId`. The card header shows live WSS status.
|
||||||
Optional: **导出全部会话** downloads `dsh-sessions-<host>-<utc>.zip` via `GET /api/netxops.sessions.export` (browser download; works for cloud Hosts).
|
Optional: **导出全部会话** downloads `dsh-sessions-<host>-<utc>.zip` via `GET /api/netxops.sessions.export` (browser download; works for cloud Hosts).
|
||||||
Capability groups: leave default for **ops**, or enable **topology** / **对其他预设公开** (new sessions after save).
|
Capability groups: **ops** defaults on (in Netx Ops + published to other presets). Enable **topology** if needed; uncheck **对其他预设公开** to keep tools Netx-Ops-only. Capability toggles auto-save (new sessions after restart if Host just healed a sparse Desktop override).
|
||||||
Optional: **知识库** — set package root (MANIFEST v1.0); default mounts `_skills/kb-*` into Netx Ops; optional public. See [KNOWLEDGE_BASE.md](KNOWLEDGE_BASE.md).
|
Optional: **知识库** — set package root (MANIFEST v1.0); default mounts `_skills/kb-*` into Netx Ops; optional public. See [KNOWLEDGE_BASE.md](KNOWLEDGE_BASE.md).
|
||||||
2. Restart after install/upgrade so the bundle patch layers apply. Open Settings → **Agent presets** → **Netx Ops** should appear.
|
2. Restart after install/upgrade so the bundle patch layers apply. Open Settings → **Agent presets** → **Netx Ops** should appear.
|
||||||
The Netx Ops preset is **standard + netxops tools/persona** (shell, search, plan, todo, web, workflows, … plus `netx__*`). Re-activate (or restart the Host) so `$DSH_HOME/.agent-presets/netxops` is recomposed from the current Harness `standard` preset.
|
The Netx Ops preset is **standard + netxops tools/persona** (shell, search, plan, todo, web, workflows, … plus `netx__*`). Re-activate (or restart the Host) so `$DSH_HOME/.agent-presets/netxops` is recomposed from the current Harness `standard` preset.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// src/netx/capability-groups.ts
|
// src/netx/capability-groups.ts
|
||||||
var DEFAULT_CAPABILITY_GROUPS = Object.freeze({
|
var DEFAULT_CAPABILITY_GROUPS = Object.freeze({
|
||||||
ops: Object.freeze({ inPreset: true, public: false }),
|
ops: Object.freeze({ inPreset: true, public: true }),
|
||||||
topology: Object.freeze({ inPreset: false, public: false }),
|
topology: Object.freeze({ inPreset: false, public: false }),
|
||||||
bizMonitor: Object.freeze({ inPreset: true, public: false })
|
bizMonitor: Object.freeze({ inPreset: true, public: false })
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// src/netx/capability-groups.ts
|
// src/netx/capability-groups.ts
|
||||||
var DEFAULT_CAPABILITY_GROUPS = Object.freeze({
|
var DEFAULT_CAPABILITY_GROUPS = Object.freeze({
|
||||||
ops: Object.freeze({ inPreset: true, public: false }),
|
ops: Object.freeze({ inPreset: true, public: true }),
|
||||||
topology: Object.freeze({ inPreset: false, public: false }),
|
topology: Object.freeze({ inPreset: false, public: false }),
|
||||||
bizMonitor: Object.freeze({ inPreset: true, public: false })
|
bizMonitor: Object.freeze({ inPreset: true, public: false })
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// src/netx/capability-groups.ts
|
// src/netx/capability-groups.ts
|
||||||
var DEFAULT_CAPABILITY_GROUPS = Object.freeze({
|
var DEFAULT_CAPABILITY_GROUPS = Object.freeze({
|
||||||
ops: Object.freeze({ inPreset: true, public: false }),
|
ops: Object.freeze({ inPreset: true, public: true }),
|
||||||
topology: Object.freeze({ inPreset: false, public: false }),
|
topology: Object.freeze({ inPreset: false, public: false }),
|
||||||
bizMonitor: Object.freeze({ inPreset: true, public: false })
|
bizMonitor: Object.freeze({ inPreset: true, public: false })
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// src/netx/capability-groups.ts
|
// src/netx/capability-groups.ts
|
||||||
var DEFAULT_CAPABILITY_GROUPS = Object.freeze({
|
var DEFAULT_CAPABILITY_GROUPS = Object.freeze({
|
||||||
ops: Object.freeze({ inPreset: true, public: false }),
|
ops: Object.freeze({ inPreset: true, public: true }),
|
||||||
topology: Object.freeze({ inPreset: false, public: false }),
|
topology: Object.freeze({ inPreset: false, public: false }),
|
||||||
bizMonitor: Object.freeze({ inPreset: true, public: false })
|
bizMonitor: Object.freeze({ inPreset: true, public: false })
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// src/netx/capability-groups.ts
|
// src/netx/capability-groups.ts
|
||||||
var DEFAULT_CAPABILITY_GROUPS = Object.freeze({
|
var DEFAULT_CAPABILITY_GROUPS = Object.freeze({
|
||||||
ops: Object.freeze({ inPreset: true, public: false }),
|
ops: Object.freeze({ inPreset: true, public: true }),
|
||||||
topology: Object.freeze({ inPreset: false, public: false }),
|
topology: Object.freeze({ inPreset: false, public: false }),
|
||||||
bizMonitor: Object.freeze({ inPreset: true, public: false })
|
bizMonitor: Object.freeze({ inPreset: true, public: false })
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// src/netx/capability-groups.ts
|
// src/netx/capability-groups.ts
|
||||||
var DEFAULT_CAPABILITY_GROUPS = Object.freeze({
|
var DEFAULT_CAPABILITY_GROUPS = Object.freeze({
|
||||||
ops: Object.freeze({ inPreset: true, public: false }),
|
ops: Object.freeze({ inPreset: true, public: true }),
|
||||||
topology: Object.freeze({ inPreset: false, public: false }),
|
topology: Object.freeze({ inPreset: false, public: false }),
|
||||||
bizMonitor: Object.freeze({ inPreset: true, public: false })
|
bizMonitor: Object.freeze({ inPreset: true, public: false })
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -2006,7 +2006,7 @@ class NetxopsCardController {
|
||||||
textField("nmsProvider"),
|
textField("nmsProvider"),
|
||||||
textField("kbRoot"),
|
textField("kbRoot"),
|
||||||
booleanFieldPersistFalse("groupOpsInPreset"),
|
booleanFieldPersistFalse("groupOpsInPreset"),
|
||||||
booleanField("groupOpsPublic"),
|
booleanFieldPersistFalse("groupOpsPublic"),
|
||||||
booleanField("groupTopologyInPreset"),
|
booleanField("groupTopologyInPreset"),
|
||||||
booleanField("groupTopologyPublic"),
|
booleanField("groupTopologyPublic"),
|
||||||
booleanFieldPersistFalse("groupBizMonitorInPreset"),
|
booleanFieldPersistFalse("groupBizMonitorInPreset"),
|
||||||
|
|
@ -2753,7 +2753,7 @@ var MEMORY_DEFAULTS = {
|
||||||
imTargetId: "",
|
imTargetId: "",
|
||||||
imTargets: "",
|
imTargets: "",
|
||||||
groupOpsInPreset: true,
|
groupOpsInPreset: true,
|
||||||
groupOpsPublic: false,
|
groupOpsPublic: true,
|
||||||
groupTopologyInPreset: false,
|
groupTopologyInPreset: false,
|
||||||
groupTopologyPublic: false,
|
groupTopologyPublic: false,
|
||||||
groupBizMonitorInPreset: false,
|
groupBizMonitorInPreset: false,
|
||||||
|
|
|
||||||
494
lib/index.js
494
lib/index.js
|
|
@ -9,63 +9,253 @@ import * as DshSettings from "@deepseek-ai/dsh-settings";
|
||||||
// src/agent-preset-install.ts
|
// src/agent-preset-install.ts
|
||||||
import {
|
import {
|
||||||
cpSync,
|
cpSync,
|
||||||
existsSync,
|
existsSync as existsSync2,
|
||||||
mkdirSync,
|
mkdirSync,
|
||||||
readFileSync,
|
readFileSync as readFileSync2,
|
||||||
rmSync,
|
rmSync,
|
||||||
writeFileSync
|
writeFileSync as writeFileSync2
|
||||||
} from "node:fs";
|
} from "node:fs";
|
||||||
import { createRequire as createRequire2 } from "node:module";
|
import { createRequire as createRequire2 } from "node:module";
|
||||||
import { homedir } from "node:os";
|
import { homedir as homedir2 } from "node:os";
|
||||||
import { dirname, join } from "node:path";
|
import { dirname, join as join2 } from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
var NETXOPS_PRESET_ID = "netxops";
|
|
||||||
|
// src/netx/heal-profile-entry.ts
|
||||||
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
||||||
|
import { homedir } from "node:os";
|
||||||
|
import { join } from "node:path";
|
||||||
var DESKTOP_PATCH_BEGIN = "# BEGIN dsh-netxops-preset (managed)";
|
var DESKTOP_PATCH_BEGIN = "# BEGIN dsh-netxops-preset (managed)";
|
||||||
var DESKTOP_PATCH_END = "# END dsh-netxops-preset (managed)";
|
var DESKTOP_PATCH_END = "# END dsh-netxops-preset (managed)";
|
||||||
var PATCH_PLUGIN_INDENT = " ";
|
var NETXOPS_HOST_CONFIG_DEFAULTS = Object.freeze({
|
||||||
function packageRoot() {
|
apiUrl: "http://127.0.0.1:8890",
|
||||||
return join(dirname(fileURLToPath(import.meta.url)), "..");
|
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: true,
|
||||||
|
groupTopologyInPreset: false,
|
||||||
|
groupTopologyPublic: false,
|
||||||
|
groupBizMonitorInPreset: true,
|
||||||
|
groupBizMonitorPublic: false,
|
||||||
|
kbRoot: "",
|
||||||
|
groupKbInPreset: true,
|
||||||
|
groupKbPublic: false
|
||||||
|
});
|
||||||
function resolveDshHome() {
|
function resolveDshHome() {
|
||||||
const fromEnv = process.env.DSH_HOME?.trim();
|
const fromEnv = process.env.DSH_HOME?.trim();
|
||||||
if (fromEnv !== undefined && fromEnv.length > 0)
|
if (fromEnv !== undefined && fromEnv.length > 0)
|
||||||
return fromEnv;
|
return fromEnv;
|
||||||
return join(homedir(), ".dsh");
|
return join(homedir(), ".dsh");
|
||||||
}
|
}
|
||||||
|
function managedLineMask(lines) {
|
||||||
|
const skip = lines.map(() => false);
|
||||||
|
let inside = false;
|
||||||
|
for (let i = 0;i < lines.length; i += 1) {
|
||||||
|
const line = lines[i];
|
||||||
|
if (line.includes(DESKTOP_PATCH_BEGIN))
|
||||||
|
inside = true;
|
||||||
|
if (inside)
|
||||||
|
skip[i] = true;
|
||||||
|
if (line.includes(DESKTOP_PATCH_END))
|
||||||
|
inside = false;
|
||||||
|
}
|
||||||
|
return skip;
|
||||||
|
}
|
||||||
|
function parseScalar(raw) {
|
||||||
|
const text = raw.trim();
|
||||||
|
if (text === "true")
|
||||||
|
return true;
|
||||||
|
if (text === "false")
|
||||||
|
return false;
|
||||||
|
if (/^-?\d+$/.test(text))
|
||||||
|
return Number(text);
|
||||||
|
if (text.startsWith('"') && text.endsWith('"') || text.startsWith("'") && text.endsWith("'")) {
|
||||||
|
return text.slice(1, -1);
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
function formatScalar(value) {
|
||||||
|
if (typeof value === "boolean" || typeof value === "number")
|
||||||
|
return String(value);
|
||||||
|
if (value === "")
|
||||||
|
return "''";
|
||||||
|
if (/[:#\n\r]|^\s|\s$/.test(value) || value.includes("'")) {
|
||||||
|
return JSON.stringify(value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
function findHostEntry(lines, skip) {
|
||||||
|
for (let i = 0;i < lines.length; i += 1) {
|
||||||
|
if (skip[i])
|
||||||
|
continue;
|
||||||
|
if (lines[i] !== "- id: netxops")
|
||||||
|
continue;
|
||||||
|
let end = i + 1;
|
||||||
|
while (end < lines.length) {
|
||||||
|
const line = lines[end];
|
||||||
|
if (skip[end])
|
||||||
|
break;
|
||||||
|
if (/^- id:/.test(line))
|
||||||
|
break;
|
||||||
|
end += 1;
|
||||||
|
}
|
||||||
|
while (end > i + 1 && lines[end - 1].trim() === "")
|
||||||
|
end -= 1;
|
||||||
|
let disabled;
|
||||||
|
let name;
|
||||||
|
const config = {};
|
||||||
|
let inConfig = false;
|
||||||
|
for (let j = i + 1;j < end; j += 1) {
|
||||||
|
const line = lines[j];
|
||||||
|
const disabledMatch = /^ disabled:\s*(.+)\s*$/.exec(line);
|
||||||
|
if (disabledMatch) {
|
||||||
|
disabled = parseScalar(disabledMatch[1]) === true;
|
||||||
|
inConfig = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const nameMatch = /^ name:\s*(.+)\s*$/.exec(line);
|
||||||
|
if (nameMatch) {
|
||||||
|
name = String(parseScalar(nameMatch[1]));
|
||||||
|
inConfig = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (/^ config:\s*$/.test(line)) {
|
||||||
|
inConfig = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (inConfig) {
|
||||||
|
const cfg = /^ ([A-Za-z0-9_]+):\s*(.*?)\s*$/.exec(line);
|
||||||
|
if (cfg) {
|
||||||
|
config[cfg[1]] = parseScalar(cfg[2]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (/^ \S/.test(line))
|
||||||
|
inConfig = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { start: i, end, disabled, name, config };
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
function needsHeal(entry) {
|
||||||
|
if (entry.name !== "dsh-netxops")
|
||||||
|
return true;
|
||||||
|
for (const key of Object.keys(NETXOPS_HOST_CONFIG_DEFAULTS)) {
|
||||||
|
if (!Object.hasOwn(entry.config, key))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
function renderHostEntry(entry) {
|
||||||
|
const config = { ...NETXOPS_HOST_CONFIG_DEFAULTS, ...entry.config };
|
||||||
|
const out = [
|
||||||
|
"- id: netxops",
|
||||||
|
" name: dsh-netxops"
|
||||||
|
];
|
||||||
|
if (entry.disabled === true)
|
||||||
|
out.push(" disabled: true");
|
||||||
|
else if (entry.disabled === false)
|
||||||
|
out.push(" disabled: false");
|
||||||
|
out.push(" config:");
|
||||||
|
for (const [key, value] of Object.entries(config)) {
|
||||||
|
out.push(` ${key}: ${formatScalar(value)}`);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
function healNetxopsProfileEntries(logger) {
|
||||||
|
let rewritten = 0;
|
||||||
|
for (const profileName of ["desktop", "web"]) {
|
||||||
|
const patchPath = join(resolveDshHome(), "profiles", profileName, "cordis.patch.yml");
|
||||||
|
if (!existsSync(patchPath))
|
||||||
|
continue;
|
||||||
|
const raw = readFileSync(patchPath, "utf8");
|
||||||
|
const nl = raw.includes(`\r
|
||||||
|
`) ? `\r
|
||||||
|
` : `
|
||||||
|
`;
|
||||||
|
const lines = raw.replace(/\r\n/g, `
|
||||||
|
`).split(`
|
||||||
|
`);
|
||||||
|
const skip = managedLineMask(lines);
|
||||||
|
const entry = findHostEntry(lines, skip);
|
||||||
|
if (entry === null)
|
||||||
|
continue;
|
||||||
|
if (!needsHeal(entry))
|
||||||
|
continue;
|
||||||
|
const rendered = renderHostEntry(entry);
|
||||||
|
const next = [
|
||||||
|
...lines.slice(0, entry.start),
|
||||||
|
...rendered,
|
||||||
|
...lines.slice(entry.end)
|
||||||
|
].join(nl);
|
||||||
|
const normalized = next.endsWith(nl) ? next : `${next}${nl}`;
|
||||||
|
if (normalized === raw)
|
||||||
|
continue;
|
||||||
|
writeFileSync(patchPath, normalized, "utf8");
|
||||||
|
rewritten += 1;
|
||||||
|
logger.info("netxops: healed sparse host entry in profiles/%s/cordis.patch.yml (restart %s to load capability defaults)", profileName, profileName === "desktop" ? "Desktop" : "dsh web");
|
||||||
|
}
|
||||||
|
return rewritten;
|
||||||
|
}
|
||||||
|
|
||||||
|
// src/agent-preset-install.ts
|
||||||
|
var NETXOPS_PRESET_ID = "netxops";
|
||||||
|
var DESKTOP_PATCH_BEGIN2 = "# BEGIN dsh-netxops-preset (managed)";
|
||||||
|
var DESKTOP_PATCH_END2 = "# END dsh-netxops-preset (managed)";
|
||||||
|
var PATCH_PLUGIN_INDENT = " ";
|
||||||
|
function packageRoot() {
|
||||||
|
return join2(dirname(fileURLToPath(import.meta.url)), "..");
|
||||||
|
}
|
||||||
|
function resolveDshHome2() {
|
||||||
|
const fromEnv = process.env.DSH_HOME?.trim();
|
||||||
|
if (fromEnv !== undefined && fromEnv.length > 0)
|
||||||
|
return fromEnv;
|
||||||
|
return join2(homedir2(), ".dsh");
|
||||||
|
}
|
||||||
function electronResourcesRoots() {
|
function electronResourcesRoots() {
|
||||||
const roots = [];
|
const roots = [];
|
||||||
const rp = process.resourcesPath;
|
const rp = process.resourcesPath;
|
||||||
if (typeof rp === "string" && rp.length > 0)
|
if (typeof rp === "string" && rp.length > 0)
|
||||||
roots.push(rp);
|
roots.push(rp);
|
||||||
roots.push(join(dirname(process.execPath), "resources"));
|
roots.push(join2(dirname(process.execPath), "resources"));
|
||||||
return roots;
|
return roots;
|
||||||
}
|
}
|
||||||
function readableFile(path) {
|
function readableFile(path) {
|
||||||
try {
|
try {
|
||||||
readFileSync(path, { encoding: "utf8", flag: "r" });
|
readFileSync2(path, { encoding: "utf8", flag: "r" });
|
||||||
return true;
|
return true;
|
||||||
} catch {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function resolveDesktopStandardPatchPath() {
|
function resolveDesktopStandardPatchPath() {
|
||||||
const dshHome = resolveDshHome();
|
const dshHome = resolveDshHome2();
|
||||||
const envOverride = process.env.NETXOPS_STANDARD_PATCH?.trim();
|
const envOverride = process.env.NETXOPS_STANDARD_PATCH?.trim();
|
||||||
const candidates = [];
|
const candidates = [];
|
||||||
if (envOverride)
|
if (envOverride)
|
||||||
candidates.push(envOverride);
|
candidates.push(envOverride);
|
||||||
for (const resources of electronResourcesRoots()) {
|
for (const resources of 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"));
|
candidates.push(join2(resources, "app.asar", "dsh", "node_modules", "@deepseek-ai", "dsh-web-app", "presets", "standard.patch.yml"), join2(resources, "app.asar.unpacked", "dsh", "node_modules", "@deepseek-ai", "dsh-web-app", "presets", "standard.patch.yml"));
|
||||||
}
|
}
|
||||||
const requireAnchors = [
|
const requireAnchors = [
|
||||||
join(dshHome, "profiles", "desktop", "package.json"),
|
join2(dshHome, "profiles", "desktop", "package.json"),
|
||||||
join(dshHome, "profiles", "desktop", "node_modules", "dsh-netxops", "package.json"),
|
join2(dshHome, "profiles", "desktop", "node_modules", "dsh-netxops", "package.json"),
|
||||||
join(packageRoot(), "package.json"),
|
join2(packageRoot(), "package.json"),
|
||||||
join(dshHome, "profiles", "node_modules", "@deepseek-ai", "dsh-web-app", "package.json"),
|
join2(dshHome, "profiles", "node_modules", "@deepseek-ai", "dsh-web-app", "package.json"),
|
||||||
join(dshHome, "profiles", "web", "package.json")
|
join2(dshHome, "profiles", "web", "package.json")
|
||||||
];
|
];
|
||||||
for (const anchor of requireAnchors) {
|
for (const anchor of requireAnchors) {
|
||||||
if (!existsSync(anchor))
|
if (!existsSync2(anchor))
|
||||||
continue;
|
continue;
|
||||||
try {
|
try {
|
||||||
const req = createRequire2(anchor);
|
const req = createRequire2(anchor);
|
||||||
|
|
@ -73,7 +263,7 @@ function resolveDesktopStandardPatchPath() {
|
||||||
candidates.push(req.resolve("@deepseek-ai/dsh-web-app/presets/standard.patch.yml"));
|
candidates.push(req.resolve("@deepseek-ai/dsh-web-app/presets/standard.patch.yml"));
|
||||||
} catch {}
|
} catch {}
|
||||||
const pkg = dirname(req.resolve("@deepseek-ai/dsh-web-app/package.json"));
|
const pkg = dirname(req.resolve("@deepseek-ai/dsh-web-app/package.json"));
|
||||||
candidates.push(join(pkg, "presets", "standard.patch.yml"));
|
candidates.push(join2(pkg, "presets", "standard.patch.yml"));
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
for (const path of candidates) {
|
for (const path of candidates) {
|
||||||
|
|
@ -83,38 +273,38 @@ function resolveDesktopStandardPatchPath() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
function resolveStandardPresetDir() {
|
function resolveStandardPresetDir() {
|
||||||
const dshHome = resolveDshHome();
|
const dshHome = resolveDshHome2();
|
||||||
const candidates = [
|
const candidates = [
|
||||||
join(dshHome, "profiles", "desktop", "node_modules", "@deepseek-ai", "dsh-agent-presets", "presets", "standard"),
|
join2(dshHome, "profiles", "desktop", "node_modules", "@deepseek-ai", "dsh-agent-presets", "presets", "standard"),
|
||||||
join(dshHome, "profiles", "web", "node_modules", "@deepseek-ai", "dsh-agent-presets", "presets", "standard"),
|
join2(dshHome, "profiles", "web", "node_modules", "@deepseek-ai", "dsh-agent-presets", "presets", "standard"),
|
||||||
join(dirname(fileURLToPath(import.meta.url)), "..", "..", "DeepSeekHarness", "packages", "preset", "agent-presets", "presets", "standard"),
|
join2(dirname(fileURLToPath(import.meta.url)), "..", "..", "DeepSeekHarness", "packages", "preset", "agent-presets", "presets", "standard"),
|
||||||
join(dshHome, "profiles", "node_modules", "@deepseek-ai", "dsh-agent-presets", "presets", "standard")
|
join2(dshHome, "profiles", "node_modules", "@deepseek-ai", "dsh-agent-presets", "presets", "standard")
|
||||||
];
|
];
|
||||||
for (const dir of candidates) {
|
for (const dir of candidates) {
|
||||||
if (existsSync(join(dir, "agent.cordis.yml")))
|
if (existsSync2(join2(dir, "agent.cordis.yml")))
|
||||||
return dir;
|
return dir;
|
||||||
}
|
}
|
||||||
for (const nm of [
|
for (const nm of [
|
||||||
join(dshHome, "profiles", "desktop", "node_modules"),
|
join2(dshHome, "profiles", "desktop", "node_modules"),
|
||||||
join(dshHome, "profiles", "web", "node_modules"),
|
join2(dshHome, "profiles", "web", "node_modules"),
|
||||||
join(dshHome, "profiles", "node_modules")
|
join2(dshHome, "profiles", "node_modules")
|
||||||
]) {
|
]) {
|
||||||
const pkgJson = join(nm, "@deepseek-ai", "dsh-agent-presets", "package.json");
|
const pkgJson = join2(nm, "@deepseek-ai", "dsh-agent-presets", "package.json");
|
||||||
if (!existsSync(pkgJson))
|
if (!existsSync2(pkgJson))
|
||||||
continue;
|
continue;
|
||||||
try {
|
try {
|
||||||
const req = createRequire2(pkgJson);
|
const req = createRequire2(pkgJson);
|
||||||
const root = dirname(req.resolve("@deepseek-ai/dsh-agent-presets/package.json"));
|
const root = dirname(req.resolve("@deepseek-ai/dsh-agent-presets/package.json"));
|
||||||
const dir = join(root, "presets", "standard");
|
const dir = join2(root, "presets", "standard");
|
||||||
if (existsSync(join(dir, "agent.cordis.yml")))
|
if (existsSync2(join2(dir, "agent.cordis.yml")))
|
||||||
return dir;
|
return dir;
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const req = createRequire2(import.meta.url);
|
const req = createRequire2(import.meta.url);
|
||||||
const root = dirname(req.resolve("@deepseek-ai/dsh-agent-presets/package.json"));
|
const root = dirname(req.resolve("@deepseek-ai/dsh-agent-presets/package.json"));
|
||||||
const dir = join(root, "presets", "standard");
|
const dir = join2(root, "presets", "standard");
|
||||||
if (existsSync(join(dir, "agent.cordis.yml")))
|
if (existsSync2(join2(dir, "agent.cordis.yml")))
|
||||||
return dir;
|
return dir;
|
||||||
} catch {}
|
} catch {}
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -123,31 +313,31 @@ function desktopRuntimeLikelyPresent() {
|
||||||
if (typeof process.versions.electron === "string")
|
if (typeof process.versions.electron === "string")
|
||||||
return true;
|
return true;
|
||||||
for (const resources of electronResourcesRoots()) {
|
for (const resources of electronResourcesRoots()) {
|
||||||
if (existsSync(join(resources, "app.asar")))
|
if (existsSync2(join2(resources, "app.asar")))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const localAppData = process.env.LOCALAPPDATA?.trim();
|
const localAppData = process.env.LOCALAPPDATA?.trim();
|
||||||
if (localAppData) {
|
if (localAppData) {
|
||||||
if (existsSync(join(localAppData, "Programs", "DeepSeek Harness", "resources", "app.asar"))) {
|
if (existsSync2(join2(localAppData, "Programs", "DeepSeek Harness", "resources", "app.asar"))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
function hostResolveBases() {
|
function hostResolveBases() {
|
||||||
const dshHome = resolveDshHome();
|
const dshHome = resolveDshHome2();
|
||||||
const bases = [];
|
const bases = [];
|
||||||
for (const resources of electronResourcesRoots()) {
|
for (const resources of electronResourcesRoots()) {
|
||||||
bases.push(join(resources, "app.asar", "dsh", "package.json"));
|
bases.push(join2(resources, "app.asar", "dsh", "package.json"));
|
||||||
bases.push(join(resources, "app.asar", "dsh", "node_modules"));
|
bases.push(join2(resources, "app.asar", "dsh", "node_modules"));
|
||||||
}
|
}
|
||||||
const localAppData = process.env.LOCALAPPDATA?.trim();
|
const localAppData = process.env.LOCALAPPDATA?.trim();
|
||||||
if (localAppData) {
|
if (localAppData) {
|
||||||
const asarDsh = join(localAppData, "Programs", "DeepSeek Harness", "resources", "app.asar", "dsh");
|
const asarDsh = join2(localAppData, "Programs", "DeepSeek Harness", "resources", "app.asar", "dsh");
|
||||||
bases.push(join(asarDsh, "package.json"), join(asarDsh, "node_modules"));
|
bases.push(join2(asarDsh, "package.json"), join2(asarDsh, "node_modules"));
|
||||||
}
|
}
|
||||||
bases.push(join(dshHome, "profiles", "desktop", "package.json"), join(dshHome, "profiles", "desktop", "node_modules"), join(dshHome, "profiles", "web", "package.json"), join(dshHome, "profiles", "web", "node_modules"), join(dshHome, "profiles", "node_modules"), join(packageRoot(), "package.json"));
|
bases.push(join2(dshHome, "profiles", "desktop", "package.json"), join2(dshHome, "profiles", "desktop", "node_modules"), join2(dshHome, "profiles", "web", "package.json"), join2(dshHome, "profiles", "web", "node_modules"), join2(dshHome, "profiles", "node_modules"), join2(packageRoot(), "package.json"));
|
||||||
return [...new Set(bases.filter((path) => path.includes("app.asar") || existsSync(path)))];
|
return [...new Set(bases.filter((path) => path.includes("app.asar") || existsSync2(path)))];
|
||||||
}
|
}
|
||||||
function packageInstalledAbove(name, bases) {
|
function packageInstalledAbove(name, bases) {
|
||||||
if (name.startsWith("cordis:") || name.startsWith("node:"))
|
if (name.startsWith("cordis:") || name.startsWith("node:"))
|
||||||
|
|
@ -156,16 +346,16 @@ function packageInstalledAbove(name, bases) {
|
||||||
for (const base of bases) {
|
for (const base of bases) {
|
||||||
let dir = base.endsWith("package.json") ? dirname(base) : base;
|
let dir = base.endsWith("package.json") ? dirname(base) : base;
|
||||||
for (;; ) {
|
for (;; ) {
|
||||||
if (readableFile(join(dir, "node_modules", pkg, "package.json")))
|
if (readableFile(join2(dir, "node_modules", pkg, "package.json")))
|
||||||
return true;
|
return true;
|
||||||
if (readableFile(join(dir, pkg, "package.json")))
|
if (readableFile(join2(dir, pkg, "package.json")))
|
||||||
return true;
|
return true;
|
||||||
const parent = dirname(dir);
|
const parent = dirname(dir);
|
||||||
if (parent === dir)
|
if (parent === dir)
|
||||||
break;
|
break;
|
||||||
dir = parent;
|
dir = parent;
|
||||||
}
|
}
|
||||||
const requireAnchor = base.endsWith("package.json") ? base : existsSync(join(base, "package.json")) ? join(base, "package.json") : null;
|
const requireAnchor = base.endsWith("package.json") ? base : existsSync2(join2(base, "package.json")) ? join2(base, "package.json") : null;
|
||||||
if (requireAnchor !== null) {
|
if (requireAnchor !== null) {
|
||||||
try {
|
try {
|
||||||
createRequire2(requireAnchor).resolve(`${pkg}/package.json`);
|
createRequire2(requireAnchor).resolve(`${pkg}/package.json`);
|
||||||
|
|
@ -339,7 +529,7 @@ function personaEntryFromMarkdown(md) {
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
function readPresetMeta(metaPath) {
|
function readPresetMeta(metaPath) {
|
||||||
const metaText = readFileSync(metaPath, "utf8");
|
const metaText = readFileSync2(metaPath, "utf8");
|
||||||
return {
|
return {
|
||||||
name: (metaText.match(/^name:\s*(.+)$/m) || [])[1]?.trim() || "Netx Ops",
|
name: (metaText.match(/^name:\s*(.+)$/m) || [])[1]?.trim() || "Netx Ops",
|
||||||
description: (metaText.match(/^description:\s*(.+)$/m) || [])[1]?.trim() || "",
|
description: (metaText.match(/^description:\s*(.+)$/m) || [])[1]?.trim() || "",
|
||||||
|
|
@ -347,13 +537,13 @@ function readPresetMeta(metaPath) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function desktopProfilePresent() {
|
function desktopProfilePresent() {
|
||||||
return existsSync(join(resolveDshHome(), "profiles", "desktop"));
|
return existsSync2(join2(resolveDshHome2(), "profiles", "desktop"));
|
||||||
}
|
}
|
||||||
function composeNetxopsPresetArtifacts() {
|
function composeNetxopsPresetArtifacts() {
|
||||||
const overlayRoot = join(packageRoot(), "presets", NETXOPS_PRESET_ID);
|
const overlayRoot = join2(packageRoot(), "presets", NETXOPS_PRESET_ID);
|
||||||
const personaPath = join(overlayRoot, "PERSONA.md");
|
const personaPath = join2(overlayRoot, "PERSONA.md");
|
||||||
const metaPath = join(overlayRoot, "preset.yml");
|
const metaPath = join2(overlayRoot, "preset.yml");
|
||||||
if (!existsSync(personaPath) || !existsSync(metaPath)) {
|
if (!existsSync2(personaPath) || !existsSync2(metaPath)) {
|
||||||
throw new Error(`bundled preset overlay missing under ${overlayRoot}`);
|
throw new Error(`bundled preset overlay missing under ${overlayRoot}`);
|
||||||
}
|
}
|
||||||
const meta = readPresetMeta(metaPath);
|
const meta = readPresetMeta(metaPath);
|
||||||
|
|
@ -368,7 +558,7 @@ function composeNetxopsPresetArtifacts() {
|
||||||
standardSource = patchPath;
|
standardSource = patchPath;
|
||||||
standardKind = "desktop-patch";
|
standardKind = "desktop-patch";
|
||||||
standardDir = null;
|
standardDir = null;
|
||||||
agentCordis = pluginsBodyToAgentCordis(extractPluginsBodyFromStandardPatch(readFileSync(patchPath, "utf8")));
|
agentCordis = pluginsBodyToAgentCordis(extractPluginsBodyFromStandardPatch(readFileSync2(patchPath, "utf8")));
|
||||||
} else {
|
} else {
|
||||||
const dir = resolveStandardPresetDir();
|
const dir = resolveStandardPresetDir();
|
||||||
if (dir === null) {
|
if (dir === null) {
|
||||||
|
|
@ -377,9 +567,9 @@ function composeNetxopsPresetArtifacts() {
|
||||||
standardSource = dir;
|
standardSource = dir;
|
||||||
standardKind = "directory";
|
standardKind = "directory";
|
||||||
standardDir = dir;
|
standardDir = dir;
|
||||||
agentCordis = readFileSync(join(dir, "agent.cordis.yml"), "utf8");
|
agentCordis = readFileSync2(join2(dir, "agent.cordis.yml"), "utf8");
|
||||||
}
|
}
|
||||||
agentCordis = replaceTopLevelEntry(agentCordis, "persona", personaEntryFromMarkdown(readFileSync(personaPath, "utf8")));
|
agentCordis = replaceTopLevelEntry(agentCordis, "persona", personaEntryFromMarkdown(readFileSync2(personaPath, "utf8")));
|
||||||
agentCordis = ensureNetxopsToolsRow(agentCordis);
|
agentCordis = ensureNetxopsToolsRow(agentCordis);
|
||||||
agentCordis = alignWorkflowEngineForHost(agentCordis, bases);
|
agentCordis = alignWorkflowEngineForHost(agentCordis, bases);
|
||||||
if (!agentCordis.endsWith(`
|
if (!agentCordis.endsWith(`
|
||||||
|
|
@ -411,23 +601,23 @@ ${pluginsBody}
|
||||||
return { standardSource, standardKind, standardDir, ...meta, agentCordis, declarativePatch };
|
return { standardSource, standardKind, standardDir, ...meta, agentCordis, declarativePatch };
|
||||||
}
|
}
|
||||||
function installDirectoryPreset(artifacts, logger) {
|
function installDirectoryPreset(artifacts, logger) {
|
||||||
const overlayRoot = join(packageRoot(), "presets", NETXOPS_PRESET_ID);
|
const overlayRoot = join2(packageRoot(), "presets", NETXOPS_PRESET_ID);
|
||||||
const destParent = join(resolveDshHome(), ".agent-presets");
|
const destParent = join2(resolveDshHome2(), ".agent-presets");
|
||||||
const dest = join(destParent, NETXOPS_PRESET_ID);
|
const dest = join2(destParent, NETXOPS_PRESET_ID);
|
||||||
mkdirSync(destParent, { recursive: true });
|
mkdirSync(destParent, { recursive: true });
|
||||||
if (existsSync(dest))
|
if (existsSync2(dest))
|
||||||
rmSync(dest, { recursive: true, force: true });
|
rmSync(dest, { recursive: true, force: true });
|
||||||
mkdirSync(dest, { recursive: true });
|
mkdirSync(dest, { recursive: true });
|
||||||
if (artifacts.standardDir !== null && existsSync(artifacts.standardDir)) {
|
if (artifacts.standardDir !== null && existsSync2(artifacts.standardDir)) {
|
||||||
cpSync(artifacts.standardDir, dest, { recursive: true });
|
cpSync(artifacts.standardDir, dest, { recursive: true });
|
||||||
}
|
}
|
||||||
writeFileSync(join(dest, "preset.yml"), readFileSync(join(overlayRoot, "preset.yml"), "utf8"));
|
writeFileSync2(join2(dest, "preset.yml"), readFileSync2(join2(overlayRoot, "preset.yml"), "utf8"));
|
||||||
writeFileSync(join(dest, "PERSONA.md"), readFileSync(join(overlayRoot, "PERSONA.md"), "utf8"));
|
writeFileSync2(join2(dest, "PERSONA.md"), readFileSync2(join2(overlayRoot, "PERSONA.md"), "utf8"));
|
||||||
writeFileSync(join(dest, "agent.cordis.yml"), artifacts.agentCordis);
|
writeFileSync2(join2(dest, "agent.cordis.yml"), artifacts.agentCordis);
|
||||||
const skillsSrc = join(overlayRoot, "skills");
|
const skillsSrc = join2(overlayRoot, "skills");
|
||||||
if (existsSync(skillsSrc))
|
if (existsSync2(skillsSrc))
|
||||||
cpSync(skillsSrc, join(dest, "skills"), { recursive: true });
|
cpSync(skillsSrc, join2(dest, "skills"), { recursive: true });
|
||||||
writeFileSync(join(dest, ".dsh-netxops-managed"), [
|
writeFileSync2(join2(dest, ".dsh-netxops-managed"), [
|
||||||
`composed-from: ${artifacts.standardKind}`,
|
`composed-from: ${artifacts.standardKind}`,
|
||||||
`standard-source: ${artifacts.standardSource}`,
|
`standard-source: ${artifacts.standardSource}`,
|
||||||
`at: ${new Date().toISOString()}`,
|
`at: ${new Date().toISOString()}`,
|
||||||
|
|
@ -439,11 +629,11 @@ function installDirectoryPreset(artifacts, logger) {
|
||||||
function syncDeclarativeIntoProfilePatch(profileName, declarativePatch, logger) {
|
function syncDeclarativeIntoProfilePatch(profileName, declarativePatch, logger) {
|
||||||
if (profileName !== "desktop" && profileName !== "web")
|
if (profileName !== "desktop" && profileName !== "web")
|
||||||
return;
|
return;
|
||||||
const profileDir = join(resolveDshHome(), "profiles", profileName);
|
const profileDir = join2(resolveDshHome2(), "profiles", profileName);
|
||||||
const patchPath = join(profileDir, "cordis.patch.yml");
|
const patchPath = join2(profileDir, "cordis.patch.yml");
|
||||||
if (!existsSync(profileDir))
|
if (!existsSync2(profileDir))
|
||||||
return;
|
return;
|
||||||
let patch = existsSync(patchPath) ? readFileSync(patchPath, "utf8") : `[]
|
let patch = existsSync2(patchPath) ? readFileSync2(patchPath, "utf8") : `[]
|
||||||
`;
|
`;
|
||||||
patch = stripManagedPresetBlocks(patch);
|
patch = stripManagedPresetBlocks(patch);
|
||||||
patch = patch.replace(/^- id: preset-netxops\n(?: .*\n)*/m, "");
|
patch = patch.replace(/^- id: preset-netxops\n(?: .*\n)*/m, "");
|
||||||
|
|
@ -453,25 +643,25 @@ function syncDeclarativeIntoProfilePatch(profileName, declarativePatch, logger)
|
||||||
`) && patch.length > 0)
|
`) && patch.length > 0)
|
||||||
patch += `
|
patch += `
|
||||||
`;
|
`;
|
||||||
const managed = `${DESKTOP_PATCH_BEGIN}
|
const managed = `${DESKTOP_PATCH_BEGIN2}
|
||||||
${declarativePatch.trimEnd()}
|
${declarativePatch.trimEnd()}
|
||||||
${DESKTOP_PATCH_END}
|
${DESKTOP_PATCH_END2}
|
||||||
`;
|
`;
|
||||||
writeFileSync(patchPath, `${patch}${managed}`, "utf8");
|
writeFileSync2(patchPath, `${patch}${managed}`, "utf8");
|
||||||
logger.info("netxops: declarative preset synced into profiles/%s/cordis.patch.yml (restart %s to load)", profileName, profileName === "desktop" ? "Desktop" : "dsh web");
|
logger.info("netxops: declarative preset synced into profiles/%s/cordis.patch.yml (restart %s to load)", profileName, profileName === "desktop" ? "Desktop" : "dsh web");
|
||||||
}
|
}
|
||||||
function stripManagedPresetBlocks(patch) {
|
function stripManagedPresetBlocks(patch) {
|
||||||
let out = patch;
|
let out = patch;
|
||||||
for (;; ) {
|
for (;; ) {
|
||||||
const start = out.indexOf(DESKTOP_PATCH_BEGIN);
|
const start = out.indexOf(DESKTOP_PATCH_BEGIN2);
|
||||||
if (start < 0)
|
if (start < 0)
|
||||||
break;
|
break;
|
||||||
const end = out.indexOf(DESKTOP_PATCH_END, start);
|
const end = out.indexOf(DESKTOP_PATCH_END2, start);
|
||||||
if (end < 0) {
|
if (end < 0) {
|
||||||
out = out.slice(0, start);
|
out = out.slice(0, start);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let cut = end + DESKTOP_PATCH_END.length;
|
let cut = end + DESKTOP_PATCH_END2.length;
|
||||||
if (out[cut] === "\r")
|
if (out[cut] === "\r")
|
||||||
cut += 1;
|
cut += 1;
|
||||||
if (out[cut] === `
|
if (out[cut] === `
|
||||||
|
|
@ -495,7 +685,7 @@ function ensureAgentPresetInstalled(logger) {
|
||||||
logger.error("netxops: failed to install directory preset: %s", error);
|
logger.error("netxops: failed to install directory preset: %s", error);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
writeFileSync(join(packageRoot(), "presets", "netxops.preset.patch.yml"), artifacts.declarativePatch);
|
writeFileSync2(join2(packageRoot(), "presets", "netxops.preset.patch.yml"), artifacts.declarativePatch);
|
||||||
} catch {}
|
} catch {}
|
||||||
try {
|
try {
|
||||||
for (const profile of ["desktop", "web"]) {
|
for (const profile of ["desktop", "web"]) {
|
||||||
|
|
@ -504,6 +694,11 @@ function ensureAgentPresetInstalled(logger) {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("netxops: failed to sync declarative preset into profile patch: %s", error);
|
logger.error("netxops: failed to sync declarative preset into profile patch: %s", error);
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
healNetxopsProfileEntries(logger);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("netxops: failed to heal host profile entry: %s", error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// src/netx/alarm-push-status.ts
|
// src/netx/alarm-push-status.ts
|
||||||
var STORE_KEY = Symbol.for("dsh-netxops.alarm-push-status");
|
var STORE_KEY = Symbol.for("dsh-netxops.alarm-push-status");
|
||||||
|
|
@ -899,16 +1094,16 @@ async function deliverAlarmToIm(ctx, payload, options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/alarm-session.ts
|
// src/netx/alarm-session.ts
|
||||||
import { homedir as homedir2 } from "node:os";
|
import { homedir as homedir3 } from "node:os";
|
||||||
import { join as join2 } from "node:path";
|
import { join as join3 } from "node:path";
|
||||||
var PRESET_ID = "netxops";
|
var PRESET_ID = "netxops";
|
||||||
var PERMISSION_PRESET = "default";
|
var PERMISSION_PRESET = "default";
|
||||||
var sticky = null;
|
var sticky = null;
|
||||||
var deliveryChain = Promise.resolve();
|
var deliveryChain = Promise.resolve();
|
||||||
function resolveWorkspacePath() {
|
function resolveWorkspacePath() {
|
||||||
const fromEnv = process.env.DSH_HOME?.trim();
|
const fromEnv = process.env.DSH_HOME?.trim();
|
||||||
const home = fromEnv && fromEnv.length > 0 ? fromEnv : join2(homedir2(), ".dsh");
|
const home = fromEnv && fromEnv.length > 0 ? fromEnv : join3(homedir3(), ".dsh");
|
||||||
return join2(home, "workspaces", "netxops-alarms");
|
return join3(home, "workspaces", "netxops-alarms");
|
||||||
}
|
}
|
||||||
async function disposeSticky(handle) {
|
async function disposeSticky(handle) {
|
||||||
if (!handle)
|
if (!handle)
|
||||||
|
|
@ -1066,7 +1261,7 @@ async function dispatchAlarmToSinks(ctx, payload, options, sinks = {}) {
|
||||||
|
|
||||||
// src/netx/capability-groups.ts
|
// src/netx/capability-groups.ts
|
||||||
var DEFAULT_CAPABILITY_GROUPS = Object.freeze({
|
var DEFAULT_CAPABILITY_GROUPS = Object.freeze({
|
||||||
ops: Object.freeze({ inPreset: true, public: false }),
|
ops: Object.freeze({ inPreset: true, public: true }),
|
||||||
topology: Object.freeze({ inPreset: false, public: false }),
|
topology: Object.freeze({ inPreset: false, public: false }),
|
||||||
bizMonitor: Object.freeze({ inPreset: true, public: false })
|
bizMonitor: Object.freeze({ inPreset: true, public: false })
|
||||||
});
|
});
|
||||||
|
|
@ -1149,7 +1344,18 @@ function capabilityGroupsFromSettings(fields) {
|
||||||
return {
|
return {
|
||||||
ops: {
|
ops: {
|
||||||
inPreset: opsInPreset,
|
inPreset: opsInPreset,
|
||||||
public: src.groupOpsPublic === true || src.groupNmsPublic === true || src.groupCommonPublic === true || src.groupManagedNePublic === true
|
public: (() => {
|
||||||
|
if (src.groupOpsPublic !== undefined)
|
||||||
|
return src.groupOpsPublic !== false;
|
||||||
|
const legacy = [
|
||||||
|
src.groupNmsPublic,
|
||||||
|
src.groupCommonPublic,
|
||||||
|
src.groupManagedNePublic
|
||||||
|
].filter((v) => v !== undefined);
|
||||||
|
if (legacy.length === 0)
|
||||||
|
return true;
|
||||||
|
return legacy.some((v) => v === true);
|
||||||
|
})()
|
||||||
},
|
},
|
||||||
topology: {
|
topology: {
|
||||||
inPreset: src.groupTopologyInPreset === true || src.groupTopologyLayoutInPreset === true,
|
inPreset: src.groupTopologyInPreset === true || src.groupTopologyLayoutInPreset === true,
|
||||||
|
|
@ -1179,29 +1385,29 @@ function toolNamesForGroups(groupIds) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/group-skills.ts
|
// src/netx/group-skills.ts
|
||||||
import { existsSync as existsSync2 } from "node:fs";
|
import { existsSync as existsSync3 } from "node:fs";
|
||||||
import { readdir, readFile, stat } from "node:fs/promises";
|
import { readdir, readFile, stat } from "node:fs/promises";
|
||||||
import { dirname as dirname2, join as join3 } from "node:path";
|
import { dirname as dirname2, join as join4 } from "node:path";
|
||||||
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
||||||
function opsSkillsRoot() {
|
function opsSkillsRoot() {
|
||||||
const envRoot = process.env.NETX_SKILLS_ROOT?.trim();
|
const envRoot = process.env.NETX_SKILLS_ROOT?.trim();
|
||||||
if (envRoot && existsSync2(envRoot))
|
if (envRoot && existsSync3(envRoot))
|
||||||
return envRoot;
|
return envRoot;
|
||||||
const here = dirname2(fileURLToPath2(import.meta.url));
|
const here = dirname2(fileURLToPath2(import.meta.url));
|
||||||
const siblingCandidates = [
|
const siblingCandidates = [
|
||||||
join3(here, "..", "..", "..", "netx", "skills"),
|
join4(here, "..", "..", "..", "netx", "skills"),
|
||||||
join3(here, "..", "..", "netx", "skills")
|
join4(here, "..", "..", "netx", "skills")
|
||||||
];
|
];
|
||||||
for (const candidate of siblingCandidates) {
|
for (const candidate of siblingCandidates) {
|
||||||
if (existsSync2(candidate))
|
if (existsSync3(candidate))
|
||||||
return candidate;
|
return candidate;
|
||||||
}
|
}
|
||||||
const packaged = [
|
const packaged = [
|
||||||
join3(here, "..", "presets", "netxops", "skills"),
|
join4(here, "..", "presets", "netxops", "skills"),
|
||||||
join3(here, "..", "..", "presets", "netxops", "skills")
|
join4(here, "..", "..", "presets", "netxops", "skills")
|
||||||
];
|
];
|
||||||
for (const candidate of packaged) {
|
for (const candidate of packaged) {
|
||||||
if (existsSync2(candidate))
|
if (existsSync3(candidate))
|
||||||
return candidate;
|
return candidate;
|
||||||
}
|
}
|
||||||
return packaged[0];
|
return packaged[0];
|
||||||
|
|
@ -1252,7 +1458,7 @@ function stripQuotes(value) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
async function loadSkillBundle(dir) {
|
async function loadSkillBundle(dir) {
|
||||||
const skillPath = join3(dir, "SKILL.md");
|
const skillPath = join4(dir, "SKILL.md");
|
||||||
let raw;
|
let raw;
|
||||||
try {
|
try {
|
||||||
raw = await readFile(skillPath, "utf8");
|
raw = await readFile(skillPath, "utf8");
|
||||||
|
|
@ -1275,7 +1481,7 @@ async function loadSkillBundle(dir) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
async function loadGroupSkills(skillsRoot, groupId) {
|
async function loadGroupSkills(skillsRoot, groupId) {
|
||||||
const groupDir = join3(skillsRoot, SKILL_DIR_BY_GROUP[groupId]);
|
const groupDir = join4(skillsRoot, SKILL_DIR_BY_GROUP[groupId]);
|
||||||
let entries;
|
let entries;
|
||||||
try {
|
try {
|
||||||
entries = await readdir(groupDir);
|
entries = await readdir(groupDir);
|
||||||
|
|
@ -1285,7 +1491,7 @@ async function loadGroupSkills(skillsRoot, groupId) {
|
||||||
const skills = [];
|
const skills = [];
|
||||||
const skipped = [];
|
const skipped = [];
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
const full = join3(groupDir, entry);
|
const full = join4(groupDir, entry);
|
||||||
let isDir = false;
|
let isDir = false;
|
||||||
try {
|
try {
|
||||||
isDir = (await stat(full)).isDirectory();
|
isDir = (await stat(full)).isDirectory();
|
||||||
|
|
@ -1294,7 +1500,7 @@ async function loadGroupSkills(skillsRoot, groupId) {
|
||||||
}
|
}
|
||||||
if (!isDir)
|
if (!isDir)
|
||||||
continue;
|
continue;
|
||||||
const skillPath = join3(full, "SKILL.md");
|
const skillPath = join4(full, "SKILL.md");
|
||||||
let raw;
|
let raw;
|
||||||
try {
|
try {
|
||||||
raw = await readFile(skillPath, "utf8");
|
raw = await readFile(skillPath, "utf8");
|
||||||
|
|
@ -1343,7 +1549,7 @@ async function registerGroupSkills(ctx, groupIds, providerLabel) {
|
||||||
log?.warn?.("%s: skip skill dir %s (%s)", providerLabel, miss.directory, miss.reason);
|
log?.warn?.("%s: skip skill dir %s (%s)", providerLabel, miss.directory, miss.reason);
|
||||||
}
|
}
|
||||||
if (skills.length === 0) {
|
if (skills.length === 0) {
|
||||||
log?.warn?.("%s: no skills loaded for group=%s from %s", providerLabel, groupId, join3(root, SKILL_DIR_BY_GROUP[groupId]));
|
log?.warn?.("%s: no skills loaded for group=%s from %s", providerLabel, groupId, join4(root, SKILL_DIR_BY_GROUP[groupId]));
|
||||||
}
|
}
|
||||||
for (const skill of skills) {
|
for (const skill of skills) {
|
||||||
disposers.push(skillsApi.register({
|
disposers.push(skillsApi.register({
|
||||||
|
|
@ -1364,8 +1570,8 @@ async function registerGroupSkills(ctx, groupIds, providerLabel) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/kb-local-path.ts
|
// src/netx/kb-local-path.ts
|
||||||
import { existsSync as existsSync3, realpathSync, statSync } from "node:fs";
|
import { existsSync as existsSync4, realpathSync, statSync } from "node:fs";
|
||||||
import { join as join4, relative, resolve, sep } from "node:path";
|
import { join as join5, relative, resolve, sep } from "node:path";
|
||||||
var KB_LOCAL_WRITABLE_ROOTS = Object.freeze([
|
var KB_LOCAL_WRITABLE_ROOTS = Object.freeze([
|
||||||
"memories",
|
"memories",
|
||||||
"drafts",
|
"drafts",
|
||||||
|
|
@ -1476,7 +1682,7 @@ function resolveExistingWritableFile(localRoot, pathArg) {
|
||||||
}
|
}
|
||||||
const checked = resolveWritableLocalPath(localRoot, ...relative(localRoot, absolutePath).split(/[/\\]/).filter(Boolean));
|
const checked = resolveWritableLocalPath(localRoot, ...relative(localRoot, absolutePath).split(/[/\\]/).filter(Boolean));
|
||||||
try {
|
try {
|
||||||
if (existsSync3(checked.absolutePath)) {
|
if (existsSync4(checked.absolutePath)) {
|
||||||
const realFile = realpathSync(checked.absolutePath);
|
const realFile = realpathSync(checked.absolutePath);
|
||||||
const realLocal = realpathSync(localRoot);
|
const realLocal = realpathSync(localRoot);
|
||||||
const realRel = relative(realLocal, realFile);
|
const realRel = relative(realLocal, realFile);
|
||||||
|
|
@ -1504,16 +1710,16 @@ function resolveExistingWritableFile(localRoot, pathArg) {
|
||||||
return checked;
|
return checked;
|
||||||
}
|
}
|
||||||
function memoryDir(bucket) {
|
function memoryDir(bucket) {
|
||||||
return join4("memories", MEMORY_BUCKET_DIR[bucket]);
|
return join5("memories", MEMORY_BUCKET_DIR[bucket]);
|
||||||
}
|
}
|
||||||
function suggestionDir(kind) {
|
function suggestionDir(kind) {
|
||||||
return join4("suggestions", kind);
|
return join5("suggestions", kind);
|
||||||
}
|
}
|
||||||
function draftDir(domain) {
|
function draftDir(domain) {
|
||||||
const d = domain?.trim();
|
const d = domain?.trim();
|
||||||
if (!d)
|
if (!d)
|
||||||
return "drafts";
|
return "drafts";
|
||||||
return join4("drafts", sanitizeSlug(d, "misc"));
|
return join5("drafts", sanitizeSlug(d, "misc"));
|
||||||
}
|
}
|
||||||
function refDir(area, device) {
|
function refDir(area, device) {
|
||||||
if (!REF_AREAS.includes(area)) {
|
if (!REF_AREAS.includes(area)) {
|
||||||
|
|
@ -1524,9 +1730,9 @@ function refDir(area, device) {
|
||||||
if (!host) {
|
if (!host) {
|
||||||
throw new Error("refs/devices requires device (host_name)");
|
throw new Error("refs/devices requires device (host_name)");
|
||||||
}
|
}
|
||||||
return join4("refs", "devices", host);
|
return join5("refs", "devices", host);
|
||||||
}
|
}
|
||||||
return join4("refs", area);
|
return join5("refs", area);
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/kb-context-skill.ts
|
// src/netx/kb-context-skill.ts
|
||||||
|
|
@ -1619,8 +1825,8 @@ function registerKbContextSkill(ctx, snapshot) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/kb-manifest.ts
|
// src/netx/kb-manifest.ts
|
||||||
import { existsSync as existsSync4, readdirSync, readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
|
import { existsSync as existsSync5, readdirSync, readFileSync as readFileSync3, statSync as statSync2 } from "node:fs";
|
||||||
import { basename, dirname as dirname3, join as join5, resolve as resolve2 } from "node:path";
|
import { basename, dirname as dirname3, join as join6, resolve as resolve2 } from "node:path";
|
||||||
var EMPTY_CONTENT = {
|
var EMPTY_CONTENT = {
|
||||||
hasRegions: false,
|
hasRegions: false,
|
||||||
hasTheory: false,
|
hasTheory: false,
|
||||||
|
|
@ -1673,8 +1879,8 @@ function findManifest(kbRoot, maxDepth = 3) {
|
||||||
if (!rootStat.isDirectory()) {
|
if (!rootStat.isDirectory()) {
|
||||||
return { paths: [], error: `kbRoot is not a directory: ${root}` };
|
return { paths: [], error: `kbRoot is not a directory: ${root}` };
|
||||||
}
|
}
|
||||||
const direct = join5(root, "MANIFEST.json");
|
const direct = join6(root, "MANIFEST.json");
|
||||||
if (existsSync4(direct)) {
|
if (existsSync5(direct)) {
|
||||||
try {
|
try {
|
||||||
if (statSync2(direct).isFile())
|
if (statSync2(direct).isFile())
|
||||||
return { paths: [direct] };
|
return { paths: [direct] };
|
||||||
|
|
@ -1684,8 +1890,8 @@ function findManifest(kbRoot, maxDepth = 3) {
|
||||||
const walk = (dir, depth) => {
|
const walk = (dir, depth) => {
|
||||||
if (depth > maxDepth)
|
if (depth > maxDepth)
|
||||||
return;
|
return;
|
||||||
const candidate = join5(dir, "MANIFEST.json");
|
const candidate = join6(dir, "MANIFEST.json");
|
||||||
if (existsSync4(candidate)) {
|
if (existsSync5(candidate)) {
|
||||||
try {
|
try {
|
||||||
if (statSync2(candidate).isFile())
|
if (statSync2(candidate).isFile())
|
||||||
found.push(candidate);
|
found.push(candidate);
|
||||||
|
|
@ -1702,7 +1908,7 @@ function findManifest(kbRoot, maxDepth = 3) {
|
||||||
for (const name of entries) {
|
for (const name of entries) {
|
||||||
if (name === "node_modules" || name === ".git")
|
if (name === "node_modules" || name === ".git")
|
||||||
continue;
|
continue;
|
||||||
const full = join5(dir, name);
|
const full = join6(dir, name);
|
||||||
let st;
|
let st;
|
||||||
try {
|
try {
|
||||||
st = statSync2(full);
|
st = statSync2(full);
|
||||||
|
|
@ -1722,7 +1928,7 @@ function findManifest(kbRoot, maxDepth = 3) {
|
||||||
for (const name of topEntries) {
|
for (const name of topEntries) {
|
||||||
if (name === "node_modules" || name === ".git")
|
if (name === "node_modules" || name === ".git")
|
||||||
continue;
|
continue;
|
||||||
const full = join5(root, name);
|
const full = join6(root, name);
|
||||||
let st;
|
let st;
|
||||||
try {
|
try {
|
||||||
st = statSync2(full);
|
st = statSync2(full);
|
||||||
|
|
@ -1840,7 +2046,7 @@ function resolveKbRoot(kbRoot, maxDepth = 3) {
|
||||||
const manifestPath = located.paths[0];
|
const manifestPath = located.paths[0];
|
||||||
let raw;
|
let raw;
|
||||||
try {
|
try {
|
||||||
raw = readFileSync2(manifestPath, "utf8");
|
raw = readFileSync3(manifestPath, "utf8");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorSnapshot(`cannot read MANIFEST: ${error instanceof Error ? error.message : String(error)}`);
|
return errorSnapshot(`cannot read MANIFEST: ${error instanceof Error ? error.message : String(error)}`);
|
||||||
}
|
}
|
||||||
|
|
@ -1866,15 +2072,15 @@ import { defineTool } from "@deepseek-ai/dsh-tools";
|
||||||
|
|
||||||
// src/netx/kb-local-ops.ts
|
// src/netx/kb-local-ops.ts
|
||||||
import {
|
import {
|
||||||
existsSync as existsSync5,
|
existsSync as existsSync6,
|
||||||
mkdirSync as mkdirSync2,
|
mkdirSync as mkdirSync2,
|
||||||
readdirSync as readdirSync2,
|
readdirSync as readdirSync2,
|
||||||
readFileSync as readFileSync3,
|
readFileSync as readFileSync4,
|
||||||
rmSync as rmSync2,
|
rmSync as rmSync2,
|
||||||
statSync as statSync3,
|
statSync as statSync3,
|
||||||
writeFileSync as writeFileSync2
|
writeFileSync as writeFileSync3
|
||||||
} from "node:fs";
|
} from "node:fs";
|
||||||
import { dirname as dirname4, join as join6 } from "node:path";
|
import { dirname as dirname4, join as join7 } from "node:path";
|
||||||
function requireLocalRoot(snapshot) {
|
function requireLocalRoot(snapshot) {
|
||||||
const root = resolveKbLocalRoot(snapshot);
|
const root = resolveKbLocalRoot(snapshot);
|
||||||
if (!root) {
|
if (!root) {
|
||||||
|
|
@ -1920,12 +2126,12 @@ function createMemory(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const name = memoryFileName({ date: args.date, slug: args.slug });
|
const name = memoryFileName({ date: args.date, slug: args.slug });
|
||||||
const target = resolveWritableLocalPath(localRoot, memoryDir(args.bucket), name);
|
const target = resolveWritableLocalPath(localRoot, memoryDir(args.bucket), name);
|
||||||
if (existsSync5(target.absolutePath) && !args.overwrite) {
|
if (existsSync6(target.absolutePath) && !args.overwrite) {
|
||||||
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
mkdirSync2(dirname4(target.absolutePath), { recursive: true });
|
mkdirSync2(dirname4(target.absolutePath), { recursive: true });
|
||||||
const existed = existsSync5(target.absolutePath);
|
const existed = existsSync6(target.absolutePath);
|
||||||
writeFileSync2(target.absolutePath, args.body.endsWith(`
|
writeFileSync3(target.absolutePath, args.body.endsWith(`
|
||||||
`) ? args.body : `${args.body}
|
`) ? args.body : `${args.body}
|
||||||
`, "utf8");
|
`, "utf8");
|
||||||
return {
|
return {
|
||||||
|
|
@ -1939,12 +2145,12 @@ function createDraft(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const name = draftFileName({ date: args.date, slug: args.slug });
|
const name = draftFileName({ date: args.date, slug: args.slug });
|
||||||
const target = resolveWritableLocalPath(localRoot, draftDir(args.domain), name);
|
const target = resolveWritableLocalPath(localRoot, draftDir(args.domain), name);
|
||||||
if (existsSync5(target.absolutePath) && !args.overwrite) {
|
if (existsSync6(target.absolutePath) && !args.overwrite) {
|
||||||
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
mkdirSync2(dirname4(target.absolutePath), { recursive: true });
|
mkdirSync2(dirname4(target.absolutePath), { recursive: true });
|
||||||
const existed = existsSync5(target.absolutePath);
|
const existed = existsSync6(target.absolutePath);
|
||||||
writeFileSync2(target.absolutePath, ensureDraftBody(args.body), "utf8");
|
writeFileSync3(target.absolutePath, ensureDraftBody(args.body), "utf8");
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
action: existed ? "updated" : "created",
|
action: existed ? "updated" : "created",
|
||||||
|
|
@ -1956,12 +2162,12 @@ function createSuggestion(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const name = suggestionFileName({ date: args.date, slug: args.slug });
|
const name = suggestionFileName({ date: args.date, slug: args.slug });
|
||||||
const target = resolveWritableLocalPath(localRoot, suggestionDir(args.kind), name);
|
const target = resolveWritableLocalPath(localRoot, suggestionDir(args.kind), name);
|
||||||
if (existsSync5(target.absolutePath) && !args.overwrite) {
|
if (existsSync6(target.absolutePath) && !args.overwrite) {
|
||||||
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
mkdirSync2(dirname4(target.absolutePath), { recursive: true });
|
mkdirSync2(dirname4(target.absolutePath), { recursive: true });
|
||||||
const existed = existsSync5(target.absolutePath);
|
const existed = existsSync6(target.absolutePath);
|
||||||
writeFileSync2(target.absolutePath, args.body.endsWith(`
|
writeFileSync3(target.absolutePath, args.body.endsWith(`
|
||||||
`) ? args.body : `${args.body}
|
`) ? args.body : `${args.body}
|
||||||
`, "utf8");
|
`, "utf8");
|
||||||
return {
|
return {
|
||||||
|
|
@ -1975,12 +2181,12 @@ function createRef(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const name = refFileName(args.slug);
|
const name = refFileName(args.slug);
|
||||||
const target = resolveWritableLocalPath(localRoot, refDir(args.area, args.device), name);
|
const target = resolveWritableLocalPath(localRoot, refDir(args.area, args.device), name);
|
||||||
if (existsSync5(target.absolutePath) && !args.overwrite) {
|
if (existsSync6(target.absolutePath) && !args.overwrite) {
|
||||||
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
mkdirSync2(dirname4(target.absolutePath), { recursive: true });
|
mkdirSync2(dirname4(target.absolutePath), { recursive: true });
|
||||||
const existed = existsSync5(target.absolutePath);
|
const existed = existsSync6(target.absolutePath);
|
||||||
writeFileSync2(target.absolutePath, args.body.endsWith(`
|
writeFileSync3(target.absolutePath, args.body.endsWith(`
|
||||||
`) ? args.body : `${args.body}
|
`) ? args.body : `${args.body}
|
||||||
`, "utf8");
|
`, "utf8");
|
||||||
return {
|
return {
|
||||||
|
|
@ -1993,13 +2199,13 @@ function createRef(snapshot, args) {
|
||||||
function updateLocalFile(snapshot, args) {
|
function updateLocalFile(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const target = resolveExistingWritableFile(localRoot, args.path);
|
const target = resolveExistingWritableFile(localRoot, args.path);
|
||||||
if (!existsSync5(target.absolutePath)) {
|
if (!existsSync6(target.absolutePath)) {
|
||||||
throw new Error(`file not found: ${target.relativePath}`);
|
throw new Error(`file not found: ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
const body = target.writableRoot === "drafts" ? ensureDraftBody(args.body) : args.body.endsWith(`
|
const body = target.writableRoot === "drafts" ? ensureDraftBody(args.body) : args.body.endsWith(`
|
||||||
`) ? args.body : `${args.body}
|
`) ? args.body : `${args.body}
|
||||||
`;
|
`;
|
||||||
writeFileSync2(target.absolutePath, body, "utf8");
|
writeFileSync3(target.absolutePath, body, "utf8");
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
action: "updated",
|
action: "updated",
|
||||||
|
|
@ -2010,7 +2216,7 @@ function updateLocalFile(snapshot, args) {
|
||||||
function deleteLocalFile(snapshot, args) {
|
function deleteLocalFile(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const target = resolveExistingWritableFile(localRoot, args.path);
|
const target = resolveExistingWritableFile(localRoot, args.path);
|
||||||
if (!existsSync5(target.absolutePath)) {
|
if (!existsSync6(target.absolutePath)) {
|
||||||
throw new Error(`file not found: ${target.relativePath}`);
|
throw new Error(`file not found: ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
rmSync2(target.absolutePath, { force: false });
|
rmSync2(target.absolutePath, { force: false });
|
||||||
|
|
@ -2031,7 +2237,7 @@ function walkFiles(dir, baseLocal, out) {
|
||||||
for (const name of entries) {
|
for (const name of entries) {
|
||||||
if (name === "." || name === "..")
|
if (name === "." || name === "..")
|
||||||
continue;
|
continue;
|
||||||
const full = join6(dir, name);
|
const full = join7(dir, name);
|
||||||
let st;
|
let st;
|
||||||
try {
|
try {
|
||||||
st = statSync3(full);
|
st = statSync3(full);
|
||||||
|
|
@ -2056,7 +2262,7 @@ function listLocalFiles(snapshot, args = {}) {
|
||||||
const roots = args.root && args.root !== "all" ? [args.root] : [...KB_LOCAL_WRITABLE_ROOTS];
|
const roots = args.root && args.root !== "all" ? [args.root] : [...KB_LOCAL_WRITABLE_ROOTS];
|
||||||
const entries = [];
|
const entries = [];
|
||||||
for (const r of roots) {
|
for (const r of roots) {
|
||||||
walkFiles(join6(localRoot, r), localRoot, entries);
|
walkFiles(join7(localRoot, r), localRoot, entries);
|
||||||
}
|
}
|
||||||
entries.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
entries.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
||||||
const limit = Math.min(Math.max(args.limit ?? 50, 1), 200);
|
const limit = Math.min(Math.max(args.limit ?? 50, 1), 200);
|
||||||
|
|
@ -2304,19 +2510,19 @@ function registerKbLocalTools(ctx, snapshot = getKbContext()) {
|
||||||
|
|
||||||
// src/netx/kb-pack-skills.ts
|
// src/netx/kb-pack-skills.ts
|
||||||
import { readdir as readdir2, stat as stat2 } from "node:fs/promises";
|
import { readdir as readdir2, stat as stat2 } from "node:fs/promises";
|
||||||
import { join as join7 } from "node:path";
|
import { join as join8 } from "node:path";
|
||||||
function kbPackSkillsEnabled(snapshot) {
|
function kbPackSkillsEnabled(snapshot) {
|
||||||
return snapshot.status === "configured" && snapshot.content.hasSkills === true;
|
return snapshot.status === "configured" && snapshot.content.hasSkills === true;
|
||||||
}
|
}
|
||||||
function resolveKbSkillsRoot(snapshot) {
|
function resolveKbSkillsRoot(snapshot) {
|
||||||
const rel = snapshot.paths.skills?.trim() || "_skills";
|
const rel = snapshot.paths.skills?.trim() || "_skills";
|
||||||
return join7(snapshot.realRoot, rel);
|
return join8(snapshot.realRoot, rel);
|
||||||
}
|
}
|
||||||
function resolveKbLocalSkillsRoot(snapshot) {
|
function resolveKbLocalSkillsRoot(snapshot) {
|
||||||
const rel = snapshot.paths.localSkills?.trim();
|
const rel = snapshot.paths.localSkills?.trim();
|
||||||
if (!rel)
|
if (!rel)
|
||||||
return null;
|
return null;
|
||||||
return join7(snapshot.realRoot, rel);
|
return join8(snapshot.realRoot, rel);
|
||||||
}
|
}
|
||||||
async function registerSkillsUnderRoot(ctx, skillsApi, root, provider, options) {
|
async function registerSkillsUnderRoot(ctx, skillsApi, root, provider, options) {
|
||||||
let entries;
|
let entries;
|
||||||
|
|
@ -2332,7 +2538,7 @@ async function registerSkillsUnderRoot(ctx, skillsApi, root, provider, options)
|
||||||
return [];
|
return [];
|
||||||
const disposers = [];
|
const disposers = [];
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
const full = join7(root, entry);
|
const full = join8(root, entry);
|
||||||
let isDir = false;
|
let isDir = false;
|
||||||
try {
|
try {
|
||||||
isDir = (await stat2(full)).isDirectory();
|
isDir = (await stat2(full)).isDirectory();
|
||||||
|
|
@ -2546,7 +2752,7 @@ function watchNetxConnection(listener) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/session-export.ts
|
// src/netx/session-export.ts
|
||||||
import { homedir as homedir3, hostname as osHostname } from "node:os";
|
import { homedir as homedir4, hostname as osHostname } from "node:os";
|
||||||
|
|
||||||
// node_modules/.pnpm/fflate@0.8.3/node_modules/fflate/esm/index.mjs
|
// node_modules/.pnpm/fflate@0.8.3/node_modules/fflate/esm/index.mjs
|
||||||
import { createRequire as createRequire3 } from "module";
|
import { createRequire as createRequire3 } from "module";
|
||||||
|
|
@ -5788,7 +5994,7 @@ var Config = z.object({
|
||||||
imTargetId: vol(z.string().default("")),
|
imTargetId: vol(z.string().default("")),
|
||||||
nmsProvider: vol(z.string().default("zte-ume")),
|
nmsProvider: vol(z.string().default("zte-ume")),
|
||||||
groupOpsInPreset: vol(z.boolean().default(true)),
|
groupOpsInPreset: vol(z.boolean().default(true)),
|
||||||
groupOpsPublic: vol(z.boolean().default(false)),
|
groupOpsPublic: vol(z.boolean().default(true)),
|
||||||
groupTopologyInPreset: vol(z.boolean().default(false)),
|
groupTopologyInPreset: vol(z.boolean().default(false)),
|
||||||
groupTopologyPublic: vol(z.boolean().default(false)),
|
groupTopologyPublic: vol(z.boolean().default(false)),
|
||||||
groupBizMonitorInPreset: vol(z.boolean().default(true)),
|
groupBizMonitorInPreset: vol(z.boolean().default(true)),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "dsh-netxops",
|
"name": "dsh-netxops",
|
||||||
"version": "0.1.50",
|
"version": "0.1.51",
|
||||||
"description": "DeepSeek Harness Netx Ops: ops/topology + IM delivery + session export + knowledge-base MANIFEST/_skills + localSkills",
|
"description": "DeepSeek Harness Netx Ops: ops/topology + IM delivery + session export + knowledge-base MANIFEST/_skills + localSkills",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import { homedir } from 'node:os'
|
||||||
import { dirname, join } from 'node:path'
|
import { dirname, join } from 'node:path'
|
||||||
import { fileURLToPath } from 'node:url'
|
import { fileURLToPath } from 'node:url'
|
||||||
import type { Context } from '@deepseek-ai/cordis'
|
import type { Context } from '@deepseek-ai/cordis'
|
||||||
|
import { healNetxopsProfileEntries } from './netx/heal-profile-entry.ts'
|
||||||
|
|
||||||
export const NETXOPS_PRESET_ID = 'netxops'
|
export const NETXOPS_PRESET_ID = 'netxops'
|
||||||
|
|
||||||
|
|
@ -610,4 +611,12 @@ export function ensureAgentPresetInstalled(logger: Context['logger']): void {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('netxops: failed to sync declarative preset into profile patch: %s', error)
|
logger.error('netxops: failed to sync declarative preset into profile patch: %s', error)
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
// Plugin Manager often leaves `- id: netxops` / `disabled: false` with no
|
||||||
|
// config; DSH 0.2 replaces the whole Config → capability/public flags never
|
||||||
|
// apply. Expand missing keys from package defaults on every Host apply.
|
||||||
|
healNetxopsProfileEntries(logger)
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('netxops: failed to heal host profile entry: %s', error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -188,7 +188,8 @@ export class NetxopsCardController {
|
||||||
textField('nmsProvider'),
|
textField('nmsProvider'),
|
||||||
textField('kbRoot'),
|
textField('kbRoot'),
|
||||||
booleanFieldPersistFalse('groupOpsInPreset'),
|
booleanFieldPersistFalse('groupOpsInPreset'),
|
||||||
booleanField('groupOpsPublic'),
|
// Default is true — must persist false explicitly or "off" snaps back.
|
||||||
|
booleanFieldPersistFalse('groupOpsPublic'),
|
||||||
booleanField('groupTopologyInPreset'),
|
booleanField('groupTopologyInPreset'),
|
||||||
booleanField('groupTopologyPublic'),
|
booleanField('groupTopologyPublic'),
|
||||||
booleanFieldPersistFalse('groupBizMonitorInPreset'),
|
booleanFieldPersistFalse('groupBizMonitorInPreset'),
|
||||||
|
|
|
||||||
|
|
@ -174,7 +174,7 @@ const MEMORY_DEFAULTS: NetxopsSettings = {
|
||||||
imTargetId: '',
|
imTargetId: '',
|
||||||
imTargets: '',
|
imTargets: '',
|
||||||
groupOpsInPreset: true,
|
groupOpsInPreset: true,
|
||||||
groupOpsPublic: false,
|
groupOpsPublic: true,
|
||||||
groupTopologyInPreset: false,
|
groupTopologyInPreset: false,
|
||||||
groupTopologyPublic: false,
|
groupTopologyPublic: false,
|
||||||
groupBizMonitorInPreset: false,
|
groupBizMonitorInPreset: false,
|
||||||
|
|
|
||||||
|
|
@ -145,7 +145,7 @@ export interface Config {
|
||||||
nmsProvider: string
|
nmsProvider: string
|
||||||
/** ops tools/skills in the Netx Ops preset (default on) — NMS + CLI + paths. */
|
/** ops tools/skills in the Netx Ops preset (default on) — NMS + CLI + paths. */
|
||||||
groupOpsInPreset: boolean
|
groupOpsInPreset: boolean
|
||||||
/** Publish ops tools/skills to other presets (default off). */
|
/** Publish ops tools/skills to other presets (default on). */
|
||||||
groupOpsPublic: boolean
|
groupOpsPublic: boolean
|
||||||
/** topology canvas / layout tools in the Netx Ops preset (default off). */
|
/** topology canvas / layout tools in the Netx Ops preset (default off). */
|
||||||
groupTopologyInPreset: boolean
|
groupTopologyInPreset: boolean
|
||||||
|
|
@ -193,7 +193,7 @@ export const Config: z<Config> = z.object({
|
||||||
imTargetId: vol(z.string().default('')),
|
imTargetId: vol(z.string().default('')),
|
||||||
nmsProvider: vol(z.string().default('zte-ume')),
|
nmsProvider: vol(z.string().default('zte-ume')),
|
||||||
groupOpsInPreset: vol(z.boolean().default(true)),
|
groupOpsInPreset: vol(z.boolean().default(true)),
|
||||||
groupOpsPublic: vol(z.boolean().default(false)),
|
groupOpsPublic: vol(z.boolean().default(true)),
|
||||||
groupTopologyInPreset: vol(z.boolean().default(false)),
|
groupTopologyInPreset: vol(z.boolean().default(false)),
|
||||||
groupTopologyPublic: vol(z.boolean().default(false)),
|
groupTopologyPublic: vol(z.boolean().default(false)),
|
||||||
groupBizMonitorInPreset: vol(z.boolean().default(true)),
|
groupBizMonitorInPreset: vol(z.boolean().default(true)),
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ export interface NetxCapabilityGroupSettingsFields {
|
||||||
* Default: ops in Ops preset; topology / bizMonitor and all public off.
|
* Default: ops in Ops preset; topology / bizMonitor and all public off.
|
||||||
*/
|
*/
|
||||||
export const DEFAULT_CAPABILITY_GROUPS: NetxCapabilityGroups = Object.freeze({
|
export const DEFAULT_CAPABILITY_GROUPS: NetxCapabilityGroups = Object.freeze({
|
||||||
ops: Object.freeze({ inPreset: true, public: false }),
|
ops: Object.freeze({ inPreset: true, public: true }),
|
||||||
topology: Object.freeze({ inPreset: false, public: false }),
|
topology: Object.freeze({ inPreset: false, public: false }),
|
||||||
bizMonitor: Object.freeze({ inPreset: true, public: false }),
|
bizMonitor: Object.freeze({ inPreset: true, public: false }),
|
||||||
})
|
})
|
||||||
|
|
@ -151,10 +151,17 @@ export function capabilityGroupsFromSettings(
|
||||||
return {
|
return {
|
||||||
ops: {
|
ops: {
|
||||||
inPreset: opsInPreset,
|
inPreset: opsInPreset,
|
||||||
public: src.groupOpsPublic === true
|
public: (() => {
|
||||||
|| src.groupNmsPublic === true
|
if (src.groupOpsPublic !== undefined) return src.groupOpsPublic !== false
|
||||||
|| src.groupCommonPublic === true
|
const legacy = [
|
||||||
|| src.groupManagedNePublic === true,
|
src.groupNmsPublic,
|
||||||
|
src.groupCommonPublic,
|
||||||
|
src.groupManagedNePublic,
|
||||||
|
].filter((v): v is boolean => v !== undefined)
|
||||||
|
// No explicit flag → default on (other presets see ops after install).
|
||||||
|
if (legacy.length === 0) return true
|
||||||
|
return legacy.some((v) => v === true)
|
||||||
|
})(),
|
||||||
},
|
},
|
||||||
topology: {
|
topology: {
|
||||||
inPreset: src.groupTopologyInPreset === true
|
inPreset: src.groupTopologyInPreset === true
|
||||||
|
|
|
||||||
203
src/netx/heal-profile-entry.ts
Normal file
203
src/netx/heal-profile-entry.ts
Normal file
|
|
@ -0,0 +1,203 @@
|
||||||
|
/**
|
||||||
|
* Heal sparse Desktop/Web profile overrides for the host `netxops` row.
|
||||||
|
*
|
||||||
|
* Plugin Manager often writes only:
|
||||||
|
* - id: netxops
|
||||||
|
* disabled: false
|
||||||
|
* On DSH 0.2 that override **replaces the whole Config**, so capability /
|
||||||
|
* public flags never land unless we re-expand the entry. Call this on Host
|
||||||
|
* apply so every install (not just hand-edited machines) gets a usable row.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
||||||
|
import { homedir } from 'node:os'
|
||||||
|
import { join } from 'node:path'
|
||||||
|
import type { Context } from '@deepseek-ai/cordis'
|
||||||
|
|
||||||
|
const DESKTOP_PATCH_BEGIN = '# BEGIN dsh-netxops-preset (managed)'
|
||||||
|
const DESKTOP_PATCH_END = '# END dsh-netxops-preset (managed)'
|
||||||
|
|
||||||
|
/** Defaults merged into missing keys only — never overwrite user values. */
|
||||||
|
export const NETXOPS_HOST_CONFIG_DEFAULTS: Readonly<Record<string, string | boolean | number>> = Object.freeze({
|
||||||
|
apiUrl: 'http://127.0.0.1:8890',
|
||||||
|
lang: 'zh',
|
||||||
|
thinkingLanguage: 'auto',
|
||||||
|
replyLanguage: 'follow-user',
|
||||||
|
nmsProvider: 'zte-ume',
|
||||||
|
tokenCredentialRef: 'NETX_API_TOKEN',
|
||||||
|
toolCallTimeoutMs: 120_000,
|
||||||
|
installAgentPreset: true,
|
||||||
|
alarmPushEnabled: false,
|
||||||
|
alarmDeliverDsh: true,
|
||||||
|
alarmDeliverIm: false,
|
||||||
|
imBotId: '',
|
||||||
|
imTargetId: '',
|
||||||
|
imTargets: '',
|
||||||
|
groupOpsInPreset: true,
|
||||||
|
/** Default on: other presets inherit ops tools/skills after install. */
|
||||||
|
groupOpsPublic: true,
|
||||||
|
groupTopologyInPreset: false,
|
||||||
|
groupTopologyPublic: false,
|
||||||
|
groupBizMonitorInPreset: true,
|
||||||
|
groupBizMonitorPublic: false,
|
||||||
|
kbRoot: '',
|
||||||
|
groupKbInPreset: true,
|
||||||
|
groupKbPublic: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
function resolveDshHome(): string {
|
||||||
|
const fromEnv = process.env.DSH_HOME?.trim()
|
||||||
|
if (fromEnv !== undefined && fromEnv.length > 0) return fromEnv
|
||||||
|
return join(homedir(), '.dsh')
|
||||||
|
}
|
||||||
|
|
||||||
|
function managedLineMask(lines: string[]): boolean[] {
|
||||||
|
const skip = lines.map(() => false)
|
||||||
|
let inside = false
|
||||||
|
for (let i = 0; i < lines.length; i += 1) {
|
||||||
|
const line = lines[i]!
|
||||||
|
if (line.includes(DESKTOP_PATCH_BEGIN)) inside = true
|
||||||
|
if (inside) skip[i] = true
|
||||||
|
if (line.includes(DESKTOP_PATCH_END)) inside = false
|
||||||
|
}
|
||||||
|
return skip
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseScalar(raw: string): string | boolean | number {
|
||||||
|
const text = raw.trim()
|
||||||
|
if (text === 'true') return true
|
||||||
|
if (text === 'false') return false
|
||||||
|
if (/^-?\d+$/.test(text)) return Number(text)
|
||||||
|
if (
|
||||||
|
(text.startsWith('"') && text.endsWith('"'))
|
||||||
|
|| (text.startsWith("'") && text.endsWith("'"))
|
||||||
|
) {
|
||||||
|
return text.slice(1, -1)
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatScalar(value: string | boolean | number): string {
|
||||||
|
if (typeof value === 'boolean' || typeof value === 'number') return String(value)
|
||||||
|
if (value === '') return "''"
|
||||||
|
if (/[:#\n\r]|^\s|\s$/.test(value) || value.includes("'")) {
|
||||||
|
return JSON.stringify(value)
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HostEntry {
|
||||||
|
start: number
|
||||||
|
end: number
|
||||||
|
disabled?: boolean
|
||||||
|
name?: string
|
||||||
|
config: Record<string, string | boolean | number>
|
||||||
|
}
|
||||||
|
|
||||||
|
function findHostEntry(lines: string[], skip: boolean[]): HostEntry | null {
|
||||||
|
for (let i = 0; i < lines.length; i += 1) {
|
||||||
|
if (skip[i]) continue
|
||||||
|
if (lines[i] !== '- id: netxops') continue
|
||||||
|
let end = i + 1
|
||||||
|
while (end < lines.length) {
|
||||||
|
const line = lines[end]!
|
||||||
|
if (skip[end]) break
|
||||||
|
if (/^- id:/.test(line)) break
|
||||||
|
end += 1
|
||||||
|
}
|
||||||
|
while (end > i + 1 && lines[end - 1]!.trim() === '') end -= 1
|
||||||
|
|
||||||
|
let disabled: boolean | undefined
|
||||||
|
let name: string | undefined
|
||||||
|
const config: Record<string, string | boolean | number> = {}
|
||||||
|
let inConfig = false
|
||||||
|
for (let j = i + 1; j < end; j += 1) {
|
||||||
|
const line = lines[j]!
|
||||||
|
const disabledMatch = /^ disabled:\s*(.+)\s*$/.exec(line)
|
||||||
|
if (disabledMatch) {
|
||||||
|
disabled = parseScalar(disabledMatch[1]!) === true
|
||||||
|
inConfig = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const nameMatch = /^ name:\s*(.+)\s*$/.exec(line)
|
||||||
|
if (nameMatch) {
|
||||||
|
name = String(parseScalar(nameMatch[1]!))
|
||||||
|
inConfig = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (/^ config:\s*$/.test(line)) {
|
||||||
|
inConfig = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (inConfig) {
|
||||||
|
const cfg = /^ ([A-Za-z0-9_]+):\s*(.*?)\s*$/.exec(line)
|
||||||
|
if (cfg) {
|
||||||
|
config[cfg[1]!] = parseScalar(cfg[2]!)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (/^ \S/.test(line)) inConfig = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { start: i, end, disabled, name, config }
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
function needsHeal(entry: HostEntry): boolean {
|
||||||
|
if (entry.name !== 'dsh-netxops') return true
|
||||||
|
for (const key of Object.keys(NETXOPS_HOST_CONFIG_DEFAULTS)) {
|
||||||
|
if (!Object.hasOwn(entry.config, key)) return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderHostEntry(entry: HostEntry): string[] {
|
||||||
|
const config = { ...NETXOPS_HOST_CONFIG_DEFAULTS, ...entry.config }
|
||||||
|
const out: string[] = [
|
||||||
|
'- id: netxops',
|
||||||
|
" name: dsh-netxops",
|
||||||
|
]
|
||||||
|
if (entry.disabled === true) out.push(' disabled: true')
|
||||||
|
else if (entry.disabled === false) out.push(' disabled: false')
|
||||||
|
out.push(' config:')
|
||||||
|
for (const [key, value] of Object.entries(config)) {
|
||||||
|
out.push(` ${key}: ${formatScalar(value)}`)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expand sparse `netxops` host overrides in desktop/web profile patches.
|
||||||
|
* @returns number of profiles rewritten.
|
||||||
|
*/
|
||||||
|
export function healNetxopsProfileEntries(logger: Context['logger']): number {
|
||||||
|
let rewritten = 0
|
||||||
|
for (const profileName of ['desktop', 'web'] as const) {
|
||||||
|
const patchPath = join(resolveDshHome(), 'profiles', profileName, 'cordis.patch.yml')
|
||||||
|
if (!existsSync(patchPath)) continue
|
||||||
|
const raw = readFileSync(patchPath, 'utf8')
|
||||||
|
const nl = raw.includes('\r\n') ? '\r\n' : '\n'
|
||||||
|
const lines = raw.replace(/\r\n/g, '\n').split('\n')
|
||||||
|
// Drop trailing empty split artifact for rewrite math; re-join with file nl.
|
||||||
|
const skip = managedLineMask(lines)
|
||||||
|
const entry = findHostEntry(lines, skip)
|
||||||
|
if (entry === null) continue
|
||||||
|
if (!needsHeal(entry)) continue
|
||||||
|
const rendered = renderHostEntry(entry)
|
||||||
|
const next = [
|
||||||
|
...lines.slice(0, entry.start),
|
||||||
|
...rendered,
|
||||||
|
...lines.slice(entry.end),
|
||||||
|
].join(nl)
|
||||||
|
const normalized = next.endsWith(nl) ? next : `${next}${nl}`
|
||||||
|
if (normalized === raw) continue
|
||||||
|
writeFileSync(patchPath, normalized, 'utf8')
|
||||||
|
rewritten += 1
|
||||||
|
logger.info(
|
||||||
|
'netxops: healed sparse host entry in profiles/%s/cordis.patch.yml (restart %s to load capability defaults)',
|
||||||
|
profileName,
|
||||||
|
profileName === 'desktop' ? 'Desktop' : 'dsh web',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return rewritten
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue