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>
This commit is contained in:
oliver 2026-10-16 22:08:22 +08:00
parent 2def53e7c7
commit 2ef3a43c63
38 changed files with 5049 additions and 17029 deletions

View file

@ -12,8 +12,6 @@ import {
groupsForced,
kbEnabledForPlane,
SKILL_DIR_BY_GROUP,
toolNamesForGroups,
TOOLS_BY_GROUP,
} from '../src/netx/capability-groups.ts'
test('defaults: ops + bizMonitor in preset; topology and public off', () => {
@ -74,14 +72,12 @@ test('preset and host planes are symmetric; only defaults differ', () => {
})
test('null boolean flags do not resurrect defaults incorrectly', () => {
// Settings projection may emit null for volatile booleans.
const groups = capabilityGroupsFromSettings({
groupOpsInPreset: /** @type {any} */ (null),
groupOpsPublic: /** @type {any} */ (null),
groupBizMonitorInPreset: /** @type {any} */ (null),
groupBizMonitorPublic: /** @type {any} */ (null),
})
// null is not a boolean → fall back to defaults (ops+bizMonitor inPreset, public off)
assert.deepEqual(groupsForPlane(groups, 'preset'), ['ops', 'bizMonitor'])
assert.deepEqual(groupsForPlane(groups, 'host'), [])
})
@ -95,24 +91,12 @@ test('kbEnabledForPlane mirrors inPreset/public symmetry', () => {
assert.equal(kbEnabledForPlane({}, 'host'), false)
})
test('one group one skill dir; ops owns NMS + managed CLI; topology owns canvas; bizMonitor owns cutover read', () => {
test('one group one skill dir', () => {
assert.deepEqual(SKILL_DIR_BY_GROUP, {
ops: 'ops',
topology: 'topology',
bizMonitor: 'biz-monitor',
})
assert.ok(TOOLS_BY_GROUP.ops.includes('netx__findTopologyPaths'))
assert.ok(TOOLS_BY_GROUP.ops.includes('netx__execManagedNe'))
assert.ok(TOOLS_BY_GROUP.ops.includes('netx__getNeExecJob'))
assert.ok(TOOLS_BY_GROUP.ops.includes('netx__queryNmsAlarms'))
assert.ok(TOOLS_BY_GROUP.topology.includes('netx__getTopologyTree'))
assert.ok(TOOLS_BY_GROUP.topology.includes('netx__layoutTopologyView'))
assert.ok(TOOLS_BY_GROUP.topology.includes('netx__suggestSinkHubs'))
assert.equal(TOOLS_BY_GROUP.topology.includes('netx__sinkTopologyDualUnits'), false)
assert.ok(TOOLS_BY_GROUP.bizMonitor.includes('netx__getBizMonitorContext'))
assert.ok(TOOLS_BY_GROUP.bizMonitor.includes('netx__listBizMonitors'))
assert.ok(TOOLS_BY_GROUP.bizMonitor.includes('netx__listBizMonitorBatches'))
assert.ok(TOOLS_BY_GROUP.bizMonitor.includes('netx__getBizCollectCommandRaw'))
})
test('capabilityGroupsFromSettings honors explicit false for inPreset defaults', () => {
@ -124,10 +108,6 @@ test('capabilityGroupsFromSettings honors explicit false for inPreset defaults',
})
assert.deepEqual(groupsForPlane(groups, 'preset'), ['topology'])
assert.deepEqual(groupsForPlane(groups, 'public'), ['topology'])
assert.ok(toolNamesForGroups(['topology']).has('netx__layoutTopologyView'))
assert.equal(toolNamesForGroups(['topology']).has('netx__sinkTopologyDualUnits'), false)
assert.equal(toolNamesForGroups(['topology']).has('netx__queryNmsAlarms'), false)
assert.equal(toolNamesForGroups(['topology']).has('netx__getBizMonitorContext'), false)
})
test('legacy groupNms/common/managedNe map to ops; absent topology flags use legacy layout', () => {