mirror of
https://github.com/hansjone/netxops.git
synced 2026-10-16 11:30:46 +08:00
Register MANIFEST paths.localSkills alongside pack _skills.
Parse packaging-generated paths and load each local_skills/*/SKILL.md with the same hasSkills toggles and provider as shared packs. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
6dee69be31
commit
53c47fbc6a
18 changed files with 662 additions and 156 deletions
|
|
@ -1,5 +1,9 @@
|
|||
/**
|
||||
* Register operator-subset knowledge pack skills (_skills dir) into DSH when KB is configured.
|
||||
* Register operator-subset knowledge pack skills into DSH when KB is configured.
|
||||
*
|
||||
* Sources (same plane / toggles / loadSkillBundle rules):
|
||||
* - `paths.skills` or default `_skills/` when `content.hasSkills`
|
||||
* - `paths.localSkills` when present (missing field or empty/missing dir → skip)
|
||||
*/
|
||||
|
||||
import { readdir, stat } from 'node:fs/promises'
|
||||
|
|
@ -10,52 +14,55 @@ import { loadSkillBundle } from './group-skills.ts'
|
|||
|
||||
export type KbSkillPlane = 'preset' | 'public'
|
||||
|
||||
type SkillsRegister = (skill: {
|
||||
name: string
|
||||
description: string
|
||||
content: string
|
||||
path?: string
|
||||
resourceBase?: { kind: 'directory'; path: string }
|
||||
provider?: string
|
||||
source: string
|
||||
}) => () => void
|
||||
|
||||
/** True when MANIFEST says the package ships skill packs. */
|
||||
export function kbPackSkillsEnabled(snapshot: KbSnapshot): boolean {
|
||||
return snapshot.status === 'configured' && snapshot.content.hasSkills === true
|
||||
}
|
||||
|
||||
/** Shared pack root: MANIFEST `paths.skills` or legacy `_skills`. */
|
||||
export function resolveKbSkillsRoot(snapshot: KbSnapshot): string {
|
||||
const rel = snapshot.paths.skills?.trim() || '_skills'
|
||||
return join(snapshot.realRoot, rel)
|
||||
}
|
||||
|
||||
/**
|
||||
* Register every valid skill under realRoot/_skills.
|
||||
* No-op when not configured / hasSkills false / directory missing.
|
||||
* @returns disposer that unregisters all pack skills.
|
||||
* Absolute local-skills root from MANIFEST `paths.localSkills`, or null when absent.
|
||||
*/
|
||||
export async function registerKbPackSkills(
|
||||
export function resolveKbLocalSkillsRoot(snapshot: KbSnapshot): string | null {
|
||||
const rel = snapshot.paths.localSkills?.trim()
|
||||
if (!rel) return null
|
||||
return join(snapshot.realRoot, rel)
|
||||
}
|
||||
|
||||
async function registerSkillsUnderRoot(
|
||||
ctx: Context,
|
||||
snapshot: KbSnapshot,
|
||||
options: {
|
||||
enabled: boolean
|
||||
providerLabel?: string
|
||||
},
|
||||
): Promise<() => void> {
|
||||
if (!options.enabled || !kbPackSkillsEnabled(snapshot)) {
|
||||
return () => {}
|
||||
}
|
||||
|
||||
const skillsApi = (ctx as { skills?: { register: (skill: {
|
||||
name: string
|
||||
description: string
|
||||
content: string
|
||||
path?: string
|
||||
resourceBase?: { kind: 'directory'; path: string }
|
||||
provider?: string
|
||||
source: string
|
||||
}) => () => void } }).skills
|
||||
if (!skillsApi || typeof skillsApi.register !== 'function') {
|
||||
return () => {}
|
||||
}
|
||||
|
||||
const root = join(snapshot.realRoot, '_skills')
|
||||
skillsApi: { register: SkillsRegister },
|
||||
root: string,
|
||||
provider: string,
|
||||
options: { logMissing: boolean; label: string },
|
||||
): Promise<Array<() => void>> {
|
||||
let entries: string[]
|
||||
try {
|
||||
entries = await readdir(root)
|
||||
} catch {
|
||||
ctx.logger.info('netxops: kb pack skills dir missing at %s — skip', root)
|
||||
return () => {}
|
||||
if (options.logMissing) {
|
||||
ctx.logger.info('netxops: kb %s dir missing at %s — skip', options.label, root)
|
||||
}
|
||||
return []
|
||||
}
|
||||
if (entries.length === 0) return []
|
||||
|
||||
const disposers: Array<() => void> = []
|
||||
const provider = options.providerLabel ?? 'netxops-kb-pack'
|
||||
for (const entry of entries) {
|
||||
const full = join(root, entry)
|
||||
let isDir = false
|
||||
|
|
@ -70,14 +77,15 @@ export async function registerKbPackSkills(
|
|||
skill = await loadSkillBundle(full)
|
||||
} catch (error) {
|
||||
ctx.logger.warn(
|
||||
'netxops: kb pack skill load failed at %s: %s',
|
||||
'netxops: kb %s skill load failed at %s: %s',
|
||||
options.label,
|
||||
full,
|
||||
error instanceof Error ? error.message : String(error),
|
||||
)
|
||||
continue
|
||||
}
|
||||
if (!skill) {
|
||||
ctx.logger.warn('netxops: kb pack skill skipped (invalid SKILL.md): %s', full)
|
||||
ctx.logger.warn('netxops: kb %s skill skipped (invalid SKILL.md): %s', options.label, full)
|
||||
continue
|
||||
}
|
||||
disposers.push(skillsApi.register({
|
||||
|
|
@ -90,12 +98,55 @@ export async function registerKbPackSkills(
|
|||
source: 'custom',
|
||||
}))
|
||||
}
|
||||
return disposers
|
||||
}
|
||||
|
||||
/**
|
||||
* Register every valid skill under paths.skills (or `_skills`) and paths.localSkills.
|
||||
* No-op when not configured / hasSkills false.
|
||||
* localSkills: field missing or directory empty/missing → skip (no error).
|
||||
* @returns disposer that unregisters all pack skills.
|
||||
*/
|
||||
export async function registerKbPackSkills(
|
||||
ctx: Context,
|
||||
snapshot: KbSnapshot,
|
||||
options: {
|
||||
enabled: boolean
|
||||
providerLabel?: string
|
||||
},
|
||||
): Promise<() => void> {
|
||||
if (!options.enabled || !kbPackSkillsEnabled(snapshot)) {
|
||||
return () => {}
|
||||
}
|
||||
|
||||
const skillsApi = (ctx as { skills?: { register: SkillsRegister } }).skills
|
||||
if (!skillsApi || typeof skillsApi.register !== 'function') {
|
||||
return () => {}
|
||||
}
|
||||
|
||||
const provider = options.providerLabel ?? 'netxops-kb-pack'
|
||||
const disposers: Array<() => void> = []
|
||||
|
||||
const skillsRoot = resolveKbSkillsRoot(snapshot)
|
||||
disposers.push(...await registerSkillsUnderRoot(ctx, skillsApi, skillsRoot, provider, {
|
||||
logMissing: true,
|
||||
label: 'pack',
|
||||
}))
|
||||
|
||||
const localRoot = resolveKbLocalSkillsRoot(snapshot)
|
||||
if (localRoot) {
|
||||
disposers.push(...await registerSkillsUnderRoot(ctx, skillsApi, localRoot, provider, {
|
||||
logMissing: false,
|
||||
label: 'local',
|
||||
}))
|
||||
}
|
||||
|
||||
if (disposers.length > 0) {
|
||||
ctx.logger.info(
|
||||
'netxops: kb pack skills registered count=%s root=%s provider=%s',
|
||||
'netxops: kb pack skills registered count=%s skillsRoot=%s localSkills=%s provider=%s',
|
||||
disposers.length,
|
||||
root,
|
||||
skillsRoot,
|
||||
localRoot ?? '(none)',
|
||||
provider,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue