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

@ -0,0 +1,45 @@
/**
* Skill injection appends disabled-tool footnotes when tools are filtered.
*/
import assert from 'node:assert/strict'
import test from 'node:test'
import { registerGroupSkills } from '../src/netx/group-skills.ts'
test('registerGroupSkills appends disabled footnote; skips disabled groups', async () => {
/** @type {Array<{ name: string, content: string }>} */
const registered = []
const ctx = {
skills: {
register(skill) {
registered.push({ name: skill.name, content: skill.content })
return () => {}
},
},
logger: { warn() {} },
}
const dispose = await registerGroupSkills(ctx, ['ops'], 'test', {
disabledByGroup: { ops: ['netx__execManagedNe'] },
lang: 'zh',
})
assert.ok(registered.length >= 1)
assert.match(registered[0].content, /以下工具已禁用:netx__execManagedNe,不要调用/)
dispose()
registered.length = 0
const dispose2 = await registerGroupSkills(ctx, [], 'test', {
disabledByGroup: { ops: ['netx__execManagedNe'] },
})
assert.equal(registered.length, 0)
dispose2()
registered.length = 0
const dispose3 = await registerGroupSkills(ctx, ['ops'], 'test', {
disabledByGroup: { ops: ['netx__execManagedNe'] },
lang: 'en',
})
assert.match(registered[0].content, /The following tools are disabled: netx__execManagedNe/)
dispose3()
})