Restore Desktop Netx Ops preset via dual-path install.

Compose from host standard for web directories and sync a correctly indented declarative insert into the desktop profile patch (0.1.48).

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-10-10 00:12:31 +08:00
parent 2d86f55bf9
commit 7a289cf409
7 changed files with 674 additions and 109 deletions

View file

@ -1,11 +1,14 @@
/**
* Install Netx Ops as a user agent-preset by composing from the host's shipped
* `standard` preset + Netx Ops overlays (persona / tools / skills).
* Install Netx Ops agent preset for both DSH host styles:
*
* 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.
* - Web / source CLI (`@deepseek-ai/dsh-agent-presets`): directory under
* `$DSH_HOME/.agent-presets/netxops`.
* - Desktop 0.2 (`@deepseek-ai/dsh-agent-preset` + registry): declarative
* insert row in `presets/netxops.preset.patch.yml`, synced into the desktop
* profile `cordis.patch.yml` on apply (not the package bundle — keeps web safe).
*
* Both are composed from the host shipped `standard` + Netx Ops overlays
* (DSH has no preset `extends`).
*/
import {
@ -24,6 +27,9 @@ import type { Context } from '@deepseek-ai/cordis'
export const NETXOPS_PRESET_ID = 'netxops'
const DESKTOP_PATCH_BEGIN = '# BEGIN dsh-netxops-preset (managed)'
const DESKTOP_PATCH_END = '# END dsh-netxops-preset (managed)'
function packageRoot(): string {
return join(dirname(fileURLToPath(import.meta.url)), '..')
}
@ -41,6 +47,8 @@ export function resolveStandardPresetDir(): string | null {
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'),
// Source checkout used by `dsh` CLI / local Harness.
join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'DeepSeekHarness', 'packages', 'preset', 'agent-presets', 'presets', 'standard'),
]
for (const dir of candidates) {
if (existsSync(join(dir, 'agent.cordis.yml'))) return dir
@ -89,7 +97,6 @@ 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
@ -101,7 +108,6 @@ function ensureNetxopsToolsRow(cordis: string): string {
}
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 [
@ -109,70 +115,179 @@ function personaEntryFromMarkdown(md: string): string {
" name: '@deepseek-ai/dsh-persona'",
' config:',
' suffix: Your working directory is {{cwd}}.',
' # Body from presets/netxops/PERSONA.md (composed onto standard at install).',
' # Body from presets/netxops/PERSONA.md (composed onto standard).',
' 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 {
export interface NetxopsPresetArtifacts {
standardDir: string
name: string
description: string
order: string
/** Full agent.cordis.yml body (standard + overlays). */
agentCordis: string
/** Declarative `@deepseek-ai/dsh-agent-preset` patch YAML. */
declarativePatch: string
}
function readPresetMeta(metaPath: string): { name: string; description: string; order: string } {
const metaText = readFileSync(metaPath, 'utf8')
return {
name: (metaText.match(/^name:\s*(.+)$/m) || [])[1]?.trim() || 'Netx Ops',
description: (metaText.match(/^description:\s*(.+)$/m) || [])[1]?.trim() || '',
order: (metaText.match(/^order:\s*(\d+)/m) || [])[1] || '50',
}
}
/** Compose standard + Netx Ops overlays into directory + declarative artifacts. */
export function composeNetxopsPresetArtifacts(): NetxopsPresetArtifacts {
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
throw new Error(`bundled preset overlay missing under ${overlayRoot}`)
}
const standardDir = resolveStandardPresetDir()
if (standardDir === null) {
logger.warn(
'netxops: cannot find shipped standard preset (@deepseek-ai/dsh-agent-presets) — skip user-preset install',
)
return
throw new Error('cannot find shipped standard preset (@deepseek-ai/dsh-agent-presets)')
}
const meta = readPresetMeta(metaPath)
let agentCordis = replaceTopLevelEntry(
readFileSync(join(standardDir, 'agent.cordis.yml'), 'utf8'),
'persona',
personaEntryFromMarkdown(readFileSync(personaPath, 'utf8')),
)
agentCordis = ensureNetxopsToolsRow(agentCordis)
if (!agentCordis.endsWith('\n')) agentCordis += '\n'
// Match dsh-web-app/presets/*.patch.yml: plugins items are indented 10 spaces
// under ` plugins:` (8). Same-level `- id:` makes YAML treat them as
// siblings of `plugins` and the declaration fails to mount.
const pluginsBody = agentCordis
.replace(/\r\n/g, '\n')
.split('\n')
.map((line) => (line.length === 0 ? '' : ` ${line}`))
.join('\n')
const declarativePatch = `# Declarative Netx Ops preset for Desktop 0.2 (@deepseek-ai/dsh-agent-preset).
# Generated from host \`standard\` + presets/netxops overlays — do not hand-edit.
# Regenerate: bun run scripts/gen-preset-patch.mjs
- insert:
- id: preset-netxops
name: '@deepseek-ai/dsh-agent-preset'
config:
id: netxops
order: ${meta.order}
name: ${JSON.stringify(meta.name)}
description: ${JSON.stringify(meta.description)}
plugins:
${pluginsBody}
`
return { standardDir, ...meta, agentCordis, declarativePatch }
}
function installDirectoryPreset(artifacts: NetxopsPresetArtifacts, logger: Context['logger']): void {
const overlayRoot = join(packageRoot(), 'presets', NETXOPS_PRESET_ID)
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 })
mkdirSync(destParent, { recursive: true })
if (existsSync(dest)) rmSync(dest, { recursive: true, force: true })
cpSync(artifacts.standardDir, dest, { recursive: true })
writeFileSync(join(dest, 'preset.yml'), readFileSync(join(overlayRoot, 'preset.yml'), 'utf8'))
writeFileSync(join(dest, 'PERSONA.md'), readFileSync(join(overlayRoot, 'PERSONA.md'), 'utf8'))
writeFileSync(join(dest, 'agent.cordis.yml'), artifacts.agentCordis)
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: ${artifacts.standardDir}`,
`at: ${new Date().toISOString()}`,
'',
].join('\n'),
'utf8',
)
logger.info('netxops: directory preset composed from standard → %s', dest)
}
writeFileSync(join(dest, 'preset.yml'), readFileSync(metaPath, 'utf8'))
writeFileSync(join(dest, 'PERSONA.md'), readFileSync(personaPath, 'utf8'))
/**
* Sync declarative preset into Desktop's cordis.patch.yml.
* Desktop 0.2 profile roots are empty `[]` (bundles + patch compose the tree),
* so we key off the profile name — never web's directory roster.
*/
function syncDeclarativeIntoProfilePatch(
profileName: string,
declarativePatch: string,
logger: Context['logger'],
): void {
if (profileName !== 'desktop') return
const profileDir = join(resolveDshHome(), 'profiles', profileName)
const patchPath = join(profileDir, 'cordis.patch.yml')
if (!existsSync(profileDir)) return
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')
let patch = existsSync(patchPath) ? readFileSync(patchPath, 'utf8') : '[]\n'
// Drop previous managed blocks (literal markers — do not RegExp `(managed)`).
patch = stripManagedPresetBlocks(patch)
// Drop a bare enable stub left by older installs.
patch = patch.replace(/^- id: preset-netxops\n(?: .*\n)*/m, '')
if (patch.trim() === '' || patch.trim() === '[]') patch = ''
if (!patch.endsWith('\n') && patch.length > 0) patch += '\n'
const skillsSrc = join(overlayRoot, 'skills')
if (existsSync(skillsSrc)) {
cpSync(skillsSrc, join(dest, 'skills'), { recursive: true })
const managed = `${DESKTOP_PATCH_BEGIN}\n${declarativePatch.trimEnd()}\n${DESKTOP_PATCH_END}\n`
writeFileSync(patchPath, `${patch}${managed}`, 'utf8')
logger.info('netxops: declarative preset synced into profiles/%s/cordis.patch.yml (restart Desktop to load)', profileName)
}
/** Remove every BEGIN…END managed block using literal index search (markers contain `()`). */
function stripManagedPresetBlocks(patch: string): string {
let out = patch
for (;;) {
const start = out.indexOf(DESKTOP_PATCH_BEGIN)
if (start < 0) break
const end = out.indexOf(DESKTOP_PATCH_END, start)
if (end < 0) {
out = out.slice(0, start)
break
}
let cut = end + DESKTOP_PATCH_END.length
if (out[cut] === '\r') cut += 1
if (out[cut] === '\n') cut += 1
out = `${out.slice(0, start)}${out.slice(cut)}`
}
return out
}
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)
/**
* Build directory preset (web) + sync declarative patch into Desktop profiles.
*/
export function ensureAgentPresetInstalled(logger: Context['logger']): void {
let artifacts: NetxopsPresetArtifacts
try {
artifacts = composeNetxopsPresetArtifacts()
} catch (error) {
logger.error('netxops: failed to install agent preset: %s', error)
logger.warn('netxops: skip preset install: %s', error)
return
}
try {
installDirectoryPreset(artifacts, logger)
} catch (error) {
logger.error('netxops: failed to install directory preset: %s', error)
}
try {
// Refresh shipped declarative file inside the package for next bundle consumers.
writeFileSync(join(packageRoot(), 'presets', 'netxops.preset.patch.yml'), artifacts.declarativePatch)
} catch {
// package may be read-only in some installs
}
try {
for (const profile of ['desktop', 'web']) {
syncDeclarativeIntoProfilePatch(profile, artifacts.declarativePatch, logger)
}
} catch (error) {
logger.error('netxops: failed to sync declarative preset into profile patch: %s', error)
}
}