mirror of
https://github.com/hansjone/netxops.git
synced 2026-10-09 06:50:46 +08:00
Host-side netx__kb* tools jail create/update/delete under paths.local memories/drafts/suggestions; Netx Ops agent.cordis.yml now includes full standard capability rows plus ops tools. Co-authored-by: Cursor <cursoragent@cursor.com>
171 lines
5.4 KiB
TypeScript
171 lines
5.4 KiB
TypeScript
/**
|
|
* Shared agent-plane mount: tools + skills for one or more capability groups.
|
|
*/
|
|
|
|
import type { Context } from '@deepseek-ai/cordis'
|
|
import type {} from '@deepseek-ai/dsh-tools'
|
|
import {
|
|
groupsForced,
|
|
groupsForPlane,
|
|
type NetxCapabilityGroupId,
|
|
} from './capability-groups.ts'
|
|
import { registerGroupSkills } from './group-skills.ts'
|
|
import { registerKbContextSkill } from './kb-context-skill.ts'
|
|
import { registerKbLocalTools } from './kb-local-tools.ts'
|
|
import { registerKbPackSkills } from './kb-pack-skills.ts'
|
|
import { getKbContext, watchKbContext } from './kb-runtime.ts'
|
|
import { getNetxConnection, watchNetxConnection } from './runtime.ts'
|
|
import { registerNetxTools } from './tools.ts'
|
|
|
|
export interface GroupToolsPluginOptions {
|
|
/** Cordis plugin name. */
|
|
name: string
|
|
/**
|
|
* `settings` — honor inPreset/public from connection (Ops aggregate mount).
|
|
* `forced` — always register `only` groups when connection exists (per-group export).
|
|
*/
|
|
mode: 'settings' | 'forced'
|
|
/** Group allow-list; omit in settings mode to use all groups. */
|
|
only?: readonly NetxCapabilityGroupId[]
|
|
}
|
|
|
|
/**
|
|
* Apply a group-scoped tools/skills plugin on the calling (usually preset) context.
|
|
*/
|
|
export function applyGroupToolsPlugin(ctx: Context, options: GroupToolsPluginOptions): void {
|
|
let unregisterTools: (() => void) | undefined
|
|
let unregisterKbLocal: (() => void) | undefined
|
|
let unregisterSkills: (() => void) | undefined
|
|
let skillGeneration = 0
|
|
|
|
const resolveGroups = (): NetxCapabilityGroupId[] => {
|
|
const connection = getNetxConnection()
|
|
if (options.mode === 'forced') {
|
|
return groupsForced(options.only ?? [])
|
|
}
|
|
return groupsForPlane(connection?.groups, 'preset', options.only)
|
|
}
|
|
|
|
const remountTools = (): void => {
|
|
unregisterTools?.()
|
|
unregisterTools = undefined
|
|
const connection = getNetxConnection()
|
|
if (connection === undefined) {
|
|
ctx.logger.warn('%s: no connection yet — waiting for host settings bridge', options.name)
|
|
return
|
|
}
|
|
const enabled = resolveGroups()
|
|
if (enabled.length === 0) {
|
|
ctx.logger.info('%s: no groups enabled', options.name)
|
|
return
|
|
}
|
|
unregisterTools = registerNetxTools(ctx, connection, {
|
|
plane: 'preset',
|
|
only: options.mode === 'forced' ? enabled : options.only,
|
|
forceGroups: options.mode === 'forced' ? enabled : undefined,
|
|
})
|
|
ctx.logger.info(
|
|
'%s: groups=[%s] → %s tokenConfigured=%s',
|
|
options.name,
|
|
enabled.join(',') || '(none)',
|
|
connection.apiUrl,
|
|
connection.token.trim().length > 0,
|
|
)
|
|
}
|
|
|
|
const remountKbLocal = (): void => {
|
|
unregisterKbLocal?.()
|
|
unregisterKbLocal = undefined
|
|
const connection = getNetxConnection()
|
|
if (connection === undefined) return
|
|
if (connection.groupKbInPreset === false) return
|
|
unregisterKbLocal = registerKbLocalTools(ctx, getKbContext())
|
|
}
|
|
|
|
remountTools()
|
|
remountKbLocal()
|
|
const stopToolWatch = watchNetxConnection(() => {
|
|
remountTools()
|
|
remountKbLocal()
|
|
})
|
|
const stopKbToolWatch = watchKbContext(() => { remountKbLocal() })
|
|
|
|
ctx.inject(['skills'], (skillsCtx) => {
|
|
let unregisterKbSkill: (() => void) | undefined
|
|
let unregisterKbPack: (() => void) | undefined
|
|
let packGeneration = 0
|
|
const remountSkills = (): void => {
|
|
const gen = ++skillGeneration
|
|
unregisterSkills?.()
|
|
unregisterSkills = undefined
|
|
if (getNetxConnection() === undefined) return
|
|
const enabled = resolveGroups()
|
|
void registerGroupSkills(skillsCtx, enabled, options.name).then((dispose) => {
|
|
if (gen !== skillGeneration) {
|
|
dispose()
|
|
return
|
|
}
|
|
unregisterSkills = dispose
|
|
}).catch((error) => {
|
|
skillsCtx.logger.warn('%s: skill register failed: %s', options.name, error)
|
|
})
|
|
}
|
|
const remountKbSkill = (): void => {
|
|
unregisterKbSkill?.()
|
|
unregisterKbSkill = undefined
|
|
unregisterKbSkill = registerKbContextSkill(skillsCtx, getKbContext())
|
|
}
|
|
const remountKbPack = (): void => {
|
|
const gen = ++packGeneration
|
|
unregisterKbPack?.()
|
|
unregisterKbPack = undefined
|
|
const connection = getNetxConnection()
|
|
const enabled = connection?.groupKbInPreset !== false
|
|
void registerKbPackSkills(skillsCtx, getKbContext(), {
|
|
enabled,
|
|
providerLabel: `${options.name}-kb-pack`,
|
|
}).then((dispose) => {
|
|
if (gen !== packGeneration) {
|
|
dispose()
|
|
return
|
|
}
|
|
unregisterKbPack = dispose
|
|
}).catch((error) => {
|
|
skillsCtx.logger.warn('%s: kb pack skill register failed: %s', options.name, error)
|
|
})
|
|
}
|
|
|
|
remountSkills()
|
|
remountKbSkill()
|
|
remountKbPack()
|
|
const stopSkillWatch = watchNetxConnection(() => {
|
|
remountSkills()
|
|
remountKbPack()
|
|
})
|
|
const stopKbWatch = watchKbContext(() => {
|
|
remountKbSkill()
|
|
remountKbPack()
|
|
})
|
|
skillsCtx.effect(() => () => {
|
|
skillGeneration += 1
|
|
packGeneration += 1
|
|
stopSkillWatch()
|
|
stopKbWatch()
|
|
unregisterSkills?.()
|
|
unregisterSkills = undefined
|
|
unregisterKbSkill?.()
|
|
unregisterKbSkill = undefined
|
|
unregisterKbPack?.()
|
|
unregisterKbPack = undefined
|
|
}, `${options.name}: dispose skills`)
|
|
})
|
|
|
|
ctx.effect(() => () => {
|
|
stopToolWatch()
|
|
stopKbToolWatch()
|
|
unregisterTools?.()
|
|
unregisterTools = undefined
|
|
unregisterKbLocal?.()
|
|
unregisterKbLocal = undefined
|
|
}, `${options.name}: dispose tools`)
|
|
}
|