Compose Netx Ops preset from host standard instead of embedding it.

Drop the declarative @deepseek-ai/dsh-agent-preset patch (and the huge flat standard copy) so web/CLI boot no longer needs that package; install copies shipped standard then overlays persona/tools/skills.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-10-09 23:35:52 +08:00
parent 3655eeef73
commit 2d86f55bf9
9 changed files with 468 additions and 1075 deletions

178
src/agent-preset-install.ts Normal file
View file

@ -0,0 +1,178 @@
/**
* Install Netx Ops as a user agent-preset by composing from the host's shipped
* `standard` preset + Netx Ops overlays (persona / tools / skills).
*
* DSH has no preset inheritance (`extends`). Copying `standard` at install time
* is the supported way to stay aligned with the running Harness version without
* embedding a full standard composition (or `@deepseek-ai/dsh-agent-preset`)
* inside this package.
*/
import {
cpSync,
existsSync,
mkdirSync,
readFileSync,
rmSync,
writeFileSync,
} from 'node:fs'
import { createRequire } from 'node:module'
import { homedir } from 'node:os'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import type { Context } from '@deepseek-ai/cordis'
export const NETXOPS_PRESET_ID = 'netxops'
function packageRoot(): string {
return join(dirname(fileURLToPath(import.meta.url)), '..')
}
function resolveDshHome(): string {
const fromEnv = process.env.DSH_HOME?.trim()
if (fromEnv !== undefined && fromEnv.length > 0) return fromEnv
return join(homedir(), '.dsh')
}
/** Locate the shipped `standard` preset directory on this machine. */
export function resolveStandardPresetDir(): string | null {
const dshHome = resolveDshHome()
const candidates = [
join(dshHome, 'profiles', 'node_modules', '@deepseek-ai', 'dsh-agent-presets', 'presets', 'standard'),
join(dshHome, 'profiles', 'web', 'node_modules', '@deepseek-ai', 'dsh-agent-presets', 'presets', 'standard'),
join(dshHome, 'profiles', 'desktop', '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', 'node_modules'),
join(dshHome, 'profiles', 'web', 'node_modules'),
join(dshHome, 'profiles', 'desktop', 'node_modules'),
]) {
const pkgJson = join(nm, '@deepseek-ai', 'dsh-agent-presets', 'package.json')
if (!existsSync(pkgJson)) continue
try {
const req = createRequire(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 next
}
}
try {
const req = createRequire(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 {
// not resolvable from this package
}
return null
}
function replaceTopLevelEntry(cordis: string, id: string, replacement: string): string {
const lines = cordis.replace(/\r\n/g, '\n').split('\n')
const start = lines.findIndex((line) => line === `- id: ${id}`)
const block = replacement.replace(/\r\n/g, '\n').trimEnd().split('\n')
if (start < 0) {
return `${cordis.replace(/\r\n/g, '\n').trimEnd()}\n\n${block.join('\n')}\n`
}
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('\n').replace(/\n{3,}/g, '\n\n')
}
function ensureNetxopsToolsRow(cordis: string): string {
if (/^- id: netxops-tools$/m.test(cordis)) return cordis
const row = '- id: netxops-tools\n name: dsh-netxops/tools'
if (/^- id: tool-skill$/m.test(cordis)) {
// Insert immediately after the tool-skill entry.
const lines = cordis.replace(/\r\n/g, '\n').split('\n')
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('\n'), '', ...lines.slice(end)].join('\n').replace(/\n{3,}/g, '\n\n')
}
return `${cordis.replace(/\r\n/g, '\n').trimEnd()}\n\n${row}\n`
}
function personaEntryFromMarkdown(md: string): string {
// Drop the human-facing H1 (`# Netx Ops 人设…`); the cordis prefix starts at the body.
const body = md.replace(/\r\n/g, '\n').replace(/^#[^\n]*\n+/u, '').replace(/\n+$/u, '')
const indented = body.split('\n').map((line) => ` ${line}`).join('\n')
return [
'- id: persona',
" name: '@deepseek-ai/dsh-persona'",
' config:',
' suffix: Your working directory is {{cwd}}.',
' # Body from presets/netxops/PERSONA.md (composed onto standard at install).',
' prefix: |-',
indented,
].join('\n')
}
/**
* Build `~/.dsh/.agent-presets/netxops` from the host `standard` preset plus
* Netx Ops persona / tools / skills. Safe to call on every Host apply.
*/
export function ensureAgentPresetInstalled(logger: Context['logger']): void {
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)) {
logger.warn('netxops: bundled preset overlay missing under %s — skip install', overlayRoot)
return
}
const standardDir = resolveStandardPresetDir()
if (standardDir === null) {
logger.warn(
'netxops: cannot find shipped standard preset (@deepseek-ai/dsh-agent-presets) — skip user-preset install',
)
return
}
const destParent = join(resolveDshHome(), '.agent-presets')
const dest = join(destParent, NETXOPS_PRESET_ID)
try {
mkdirSync(destParent, { recursive: true })
if (existsSync(dest)) rmSync(dest, { recursive: true, force: true })
// Real copy: DSH discovery skips Windows junctions.
cpSync(standardDir, dest, { recursive: true })
writeFileSync(join(dest, 'preset.yml'), readFileSync(metaPath, 'utf8'))
writeFileSync(join(dest, 'PERSONA.md'), readFileSync(personaPath, 'utf8'))
const standardCordis = readFileSync(join(standardDir, 'agent.cordis.yml'), 'utf8')
let composed = replaceTopLevelEntry(
standardCordis,
'persona',
personaEntryFromMarkdown(readFileSync(personaPath, 'utf8')),
)
composed = ensureNetxopsToolsRow(composed)
writeFileSync(join(dest, 'agent.cordis.yml'), composed.endsWith('\n') ? composed : `${composed}\n`, 'utf8')
const skillsSrc = join(overlayRoot, 'skills')
if (existsSync(skillsSrc)) {
cpSync(skillsSrc, join(dest, 'skills'), { recursive: true })
}
writeFileSync(
join(dest, '.dsh-netxops-managed'),
[
`composed-from: standard`,
`standard-source: ${standardDir}`,
`at: ${new Date().toISOString()}`,
'',
].join('\n'),
'utf8',
)
logger.info('netxops: agent preset composed from standard → %s', dest)
} catch (error) {
logger.error('netxops: failed to install agent preset: %s', error)
}
}

View file

@ -19,16 +19,17 @@
* @module dsh-netxops
*/
import { cpSync, existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'
import { homedir } from 'node:os'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import type { Context } from '@deepseek-ai/cordis'
import z from '@deepseek-ai/schemastery'
import { credentialRef } from '@deepseek-ai/dsh-credentials'
import type {} from '@deepseek-ai/dsh-credentials'
import * as DshSettings from '@deepseek-ai/dsh-settings'
import type {} from '@deepseek-ai/dsh-settings'
export {
ensureAgentPresetInstalled,
NETXOPS_PRESET_ID,
} from './agent-preset-install.ts'
import { ensureAgentPresetInstalled } from './agent-preset-install.ts'
import { startAlarmPushClient } from './netx/alarm-push.ts'
import {
getAlarmPushStatus,
@ -87,9 +88,6 @@ export const NETXOPS_RPC_CHANNEL = '/netxops'
/** Settings / composition namespace (Plugins page join key). */
export const NETXOPS_SETTINGS_NAMESPACE = 'netxops'
/** Agent preset id under `$DSH_HOME/.agent-presets/`. */
export const NETXOPS_PRESET_ID = 'netxops'
/** Default credential reference for the netx API bearer token. */
export const DEFAULT_TOKEN_REF = 'NETX_API_TOKEN'
@ -114,11 +112,9 @@ export interface Config {
/** Per tool-call timeout (ms). */
toolCallTimeoutMs: number
/**
* On DSH ≤0.1.5: copy bundled preset into `$DSH_HOME/.agent-presets/netxops`
* (directory discovery). On DSH ≥0.2.0 that directory is dead — the
* declarative `@deepseek-ai/dsh-agent-preset` row in
* `presets/netxops.preset.patch.yml` is what the picker reads; the copy
* remains as a no-op fallback for older hosts.
* Compose `$DSH_HOME/.agent-presets/netxops` from the host shipped
* `standard` preset + Netx Ops persona/tools/skills (DSH has no preset
* inheritance). Disable only if you manage that directory yourself.
*/
installAgentPreset: boolean
/**
@ -207,48 +203,6 @@ export const Config: z<Config> = z.object({
groupKbPublic: vol(z.boolean().default(false)),
})
/** Package root (parent of `lib/` or `src/` depending on launch). */
function packageRoot(): string {
return join(dirname(fileURLToPath(import.meta.url)), '..')
}
/** Harness home used by agent-presets' user root. */
function resolveDshHome(): string {
const fromEnv = process.env.DSH_HOME?.trim()
if (fromEnv !== undefined && fromEnv.length > 0) return fromEnv
return join(homedir(), '.dsh')
}
/**
* Install the bundled Netx Ops preset as a real directory under the user
* preset root (DSH ≤0.1.5 discovery). DSH ≥0.2.0 ignores this tree — use the
* declarative patch row instead. DSH discovery skips Windows junctions
* (`Dirent.isDirectory()` is false for reparse points), so a copy is required
* — not `mklink /J`.
* @param logger - cordis logger for non-fatal install failures.
*/
export function ensureAgentPresetInstalled(logger: Context['logger']): void {
const src = join(packageRoot(), 'presets', NETXOPS_PRESET_ID)
const composition = join(src, 'agent.cordis.yml')
if (!existsSync(composition)) {
logger.warn('netxops: bundled preset missing at %s — skip user-preset install', src)
return
}
const destParent = join(resolveDshHome(), '.agent-presets')
const dest = join(destParent, NETXOPS_PRESET_ID)
try {
mkdirSync(destParent, { recursive: true })
if (existsSync(dest)) {
rmSync(dest, { recursive: true, force: true })
}
cpSync(src, dest, { recursive: true })
writeFileSync(join(dest, '.dsh-netxops-managed'), `${new Date().toISOString()}\n`, 'utf8')
logger.info('netxops: agent preset installed at %s', dest)
} catch (error) {
logger.error('netxops: failed to install agent preset: %s', error)
}
}
/**
* Resolve bearer token from the credentials seam (or empty when unset).
* Requires `inject: ['credentials']` so the store is live before apply.