netxops/test/selected-presets.test.mjs
oliver 2ef3a43c63 Netx tools: load from netx remote /mcp, add per-tool toggles
- Drop the TypeScript netx tool implementations and the hard-coded
  TOOLS_BY_GROUP list; register netx__* tools from the server's
  tools/list (group from _meta.netx.group) and forward tools/call.
- Thin Streamable HTTP client on standard fetch (fetchImpl injectable);
  cached catalog when netx is unreachable, refresh to reconnect.
- disabledTools (deny list; new server tools follow their group) and
  readOnlyOnly (only readOnlyHint=true tools), re-registered live.
- Settings card: per-group expandable tool rows with switches and a
  write marker; skills append a disabled-tools note.
- Toggles only control model visibility; netx enforces security.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 22:08:22 +08:00

106 lines
4.2 KiB
JavaScript

import test from 'node:test'
import assert from 'node:assert/strict'
import { Context, Service } from '@deepseek-ai/cordis'
import { createScope, scopeOf } from '@deepseek-ai/dsh-scope'
import { mkdtempSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { installPresetPublication } from '../src/netx/preset-publication.ts'
import { publishNetxConnection } from '../src/netx/runtime.ts'
import { capabilityGroupsFromSettings } from '../src/netx/capability-groups.ts'
import { OPS_TOOL_NAMES, clearMcpCatalogSeed, seedMcpCatalog } from './mcp-test-catalog.mjs'
const layers = new Map()
const skillLayers = new Map()
let live = []
class Tools extends Service {
constructor(ctx) { super(ctx, 'tools') }
register(entry) {
const key = scopeOf(this.ctx)
let layer = layers.get(key)
if (!layer) layers.set(key, layer = new Map())
if (layer.has(entry.name)) throw new Error(`duplicate tool ${entry.name}`)
layer.set(entry.name, entry)
return () => layer.delete(entry.name)
}
}
class Skills extends Service {
constructor(ctx) { super(ctx, 'skills') }
register(entry) {
const key = scopeOf(this.ctx)
let layer = skillLayers.get(key)
if (!layer) skillLayers.set(key, layer = new Map())
if (layer.has(entry.name)) throw new Error(`duplicate skill ${entry.name}`)
layer.set(entry.name, entry)
return () => layer.delete(entry.name)
}
}
class Agents extends Service {
constructor(ctx) { super(ctx, 'agents') }
list() { return live }
}
class Presets extends Service {
constructor(ctx) { super(ctx, 'agentPresets') }
composedPreset(ctx) { return ctx.presetId }
}
const wait = () => new Promise(resolve => setTimeout(resolve, 50))
test('selected publication uses each exact session scope and cleans up on off, switch, and unload', async t => {
const previous = process.env.DSH_HOME
process.env.DSH_HOME = mkdtempSync(join(tmpdir(), 'netxops-selected-'))
seedMcpCatalog()
const ctx = new Context()
const owners = []
t.after(async () => {
for (const owner of owners.reverse()) await owner.dispose()
clearMcpCatalogSeed()
if (previous === undefined) delete process.env.DSH_HOME; else process.env.DSH_HOME = previous
layers.clear(); skillLayers.clear(); live = []
})
owners.push(await ctx.plugin(Tools))
owners.push(await ctx.plugin(Skills))
owners.push(await ctx.plugin(Agents))
owners.push(await ctx.plugin(Presets))
const scopes = []
owners.push(await ctx.plugin(Object.assign(inner => {
for (const id of ['standard', 'review']) {
const key = {}
const scope = createScope(inner, key)
const agent = { id, ctx: scope.ctx.extend({ presetId: id }) }
scopes.push({ key, scope, agent })
}
}, { inject: ['tools', 'skills'] })))
live = scopes.map(row => row.agent)
const publish = (overrides = {}) => publishNetxConnection({
apiUrl: 'http://127.0.0.1:8890', token: '', lang: 'zh', toolCallTimeoutMs: 1000,
toolsEnabled: true,
groups: capabilityGroupsFromSettings({ groupOpsPresets: 'standard', groupBizMonitorInPreset: false }),
groupKbInPreset: false, groupKbPresets: '', ...overrides,
})
publish()
const owner = await ctx.plugin(installPresetPublication)
owners.push(owner)
await wait()
assert.equal(layers.get(scopes[0].key)?.size, OPS_TOOL_NAMES.length)
assert.equal(skillLayers.get(scopes[0].key)?.has('netx-ops'), true)
assert.equal(layers.get(scopes[1].key)?.size ?? 0, 0)
publish({ toolsEnabled: false })
await wait()
assert.equal(layers.get(scopes[0].key)?.size, 0)
assert.equal(skillLayers.get(scopes[0].key)?.size, 0)
publish()
await wait()
assert.equal(layers.get(scopes[0].key)?.size, OPS_TOOL_NAMES.length)
scopes[0].agent.ctx = scopes[0].agent.ctx.extend({ presetId: 'review' })
await ctx.serial('agent-preset/selected', 'standard', 'review')
await wait()
assert.equal(layers.get(scopes[0].key)?.size, 0)
scopes[1].agent.ctx = scopes[1].agent.ctx.extend({ presetId: 'standard' })
await ctx.serial('agent/created', { agent: scopes[1].agent })
await wait()
assert.equal(layers.get(scopes[1].key)?.size, OPS_TOOL_NAMES.length)
await owner.dispose()
await wait()
assert.equal(layers.get(scopes[1].key)?.size, 0)
assert.equal(skillLayers.get(scopes[1].key)?.size, 0)
})