dsh-server-connector 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +86 -0
- package/README.md +574 -456
- package/lib/audit.js +14 -3
- package/lib/client.js +1716 -1376
- package/lib/policy.js +167 -6
- package/lib/registry.js +182 -7
- package/lib/routes.js +1527 -1272
- package/lib/tools.js +42 -10
- package/package.json +3 -2
package/lib/tools.js
CHANGED
|
@@ -55,7 +55,7 @@ export const TOOL_NAMES = Object.freeze([
|
|
|
55
55
|
* Machine-readable error codes (§5.5). `INVALID_ARGUMENT` is the one addition:
|
|
56
56
|
* it marks a malformed tool argument, which the §5.5 table does not enumerate
|
|
57
57
|
* because it describes transport and policy failures.
|
|
58
|
-
* @typedef {'CONNECT_FAILED'|'HOST_KEY_UNKNOWN'|'AUTH_FAILED'|'POLICY_BLOCKED'|'POLICY_DENIED_BY_USER'|'TIMEOUT'|'EXIT_NONZERO'|'UNKNOWN_SERVER'|'AMBIGUOUS_SERVER'|'NO_CREDENTIAL'|'INVALID_ARGUMENT'} ToolErrorCode
|
|
58
|
+
* @typedef {'CONNECT_FAILED'|'HOST_KEY_UNKNOWN'|'AUTH_FAILED'|'POLICY_BLOCKED'|'POLICY_DENIED_BY_USER'|'TIMEOUT'|'EXIT_NONZERO'|'UNKNOWN_SERVER'|'AMBIGUOUS_SERVER'|'SERVER_DISABLED'|'NO_CREDENTIAL'|'INVALID_ARGUMENT'} ToolErrorCode
|
|
59
59
|
*/
|
|
60
60
|
|
|
61
61
|
/**
|
|
@@ -164,6 +164,10 @@ export function renderServerSection(servers, options = {}) {
|
|
|
164
164
|
`高危策略:内置 ${RULES.length} 条规则(${blocked} 条 block、${confirmed} 条 confirm),未做逐台覆盖(可用 srv_policy_check 预检)`,
|
|
165
165
|
)
|
|
166
166
|
}
|
|
167
|
+
// Custom rules are counted, not listed (doc §4.4.4): the model needs to know a
|
|
168
|
+
// server is stricter than the catalog, not what its private regexes say.
|
|
169
|
+
const customCount = servers.reduce((total, server) => total + (server.customRules ?? []).length, 0)
|
|
170
|
+
if (customCount > 0) lines.push(`另有 ${customCount} 条自定义规则(可用 srv_policy_check 预检具体命令)`)
|
|
167
171
|
return lines.join('\n')
|
|
168
172
|
}
|
|
169
173
|
|
|
@@ -413,9 +417,13 @@ export class ToolSurface {
|
|
|
413
417
|
// deployment, and neither is session-scoped.
|
|
414
418
|
order: systemPromptOrder(ctx, 'MCP_SERVERS'),
|
|
415
419
|
text: () =>
|
|
416
|
-
renderServerSection(
|
|
417
|
-
|
|
418
|
-
|
|
420
|
+
renderServerSection(
|
|
421
|
+
// Disabled servers are invisible here too (doc §5.1): the prompt is
|
|
422
|
+
// the model's map of what it may address, so listing a target that
|
|
423
|
+
// every call would reject only invites wasted attempts.
|
|
424
|
+
(this.#loaded ? this.#servers : []).filter((server) => server.enabled !== false),
|
|
425
|
+
{ maxListedServers: this.#config.maxListedServers },
|
|
426
|
+
),
|
|
419
427
|
}),
|
|
420
428
|
)
|
|
421
429
|
}
|
|
@@ -468,7 +476,11 @@ export class ToolSurface {
|
|
|
468
476
|
async execute() {
|
|
469
477
|
await surface.refresh()
|
|
470
478
|
const connected = new Set(surface.poolConnectedIds())
|
|
471
|
-
|
|
479
|
+
// A disabled server is invisible to the model (doc §5.1): it is not
|
|
480
|
+
// returned here, not listed in the system prompt, and any attempt to use
|
|
481
|
+
// it fails in #resolveServer. Showing it only to reject it would make the
|
|
482
|
+
// model retry a target it cannot reach.
|
|
483
|
+
return surface.servers.filter((server) => server.enabled !== false).map((server) => ({
|
|
472
484
|
id: server.id,
|
|
473
485
|
name: server.name,
|
|
474
486
|
host: server.host,
|
|
@@ -681,7 +693,7 @@ export class ToolSurface {
|
|
|
681
693
|
},
|
|
682
694
|
],
|
|
683
695
|
},
|
|
684
|
-
async execute(args) {
|
|
696
|
+
async execute(args, exec) {
|
|
685
697
|
const jobs = surface.#jobs
|
|
686
698
|
if (jobs === null || jobs === undefined) {
|
|
687
699
|
throw new ToolError(
|
|
@@ -691,7 +703,11 @@ export class ToolSurface {
|
|
|
691
703
|
)
|
|
692
704
|
}
|
|
693
705
|
const id = String(args.jobId)
|
|
694
|
-
|
|
706
|
+
// The owner is the calling session, so it must come from this call's
|
|
707
|
+
// `exec`. Passing `undefined` here made every read look unowned, and the
|
|
708
|
+
// registry — which fences owned jobs by session — then rejected it with
|
|
709
|
+
// "belongs to another session" even for the session that started it.
|
|
710
|
+
const caller = surface.#ownerOf(exec)
|
|
695
711
|
if (typeof args.offset === 'number') {
|
|
696
712
|
if (!Number.isFinite(args.offset) || args.offset < 0) {
|
|
697
713
|
throw new ToolError('INVALID_ARGUMENT', 'offset must be a non-negative byte offset.')
|
|
@@ -752,7 +768,7 @@ export class ToolSurface {
|
|
|
752
768
|
},
|
|
753
769
|
],
|
|
754
770
|
},
|
|
755
|
-
async execute(args) {
|
|
771
|
+
async execute(args, exec) {
|
|
756
772
|
const jobs = surface.#jobs
|
|
757
773
|
if (jobs === null || jobs === undefined) {
|
|
758
774
|
throw new ToolError(
|
|
@@ -762,7 +778,8 @@ export class ToolSurface {
|
|
|
762
778
|
)
|
|
763
779
|
}
|
|
764
780
|
const id = String(args.jobId)
|
|
765
|
-
|
|
781
|
+
// Same owner fence as srv_job_output: the caller is this call's session.
|
|
782
|
+
const caller = surface.#ownerOf(exec)
|
|
766
783
|
const outcome = jobs.kill(id, caller)
|
|
767
784
|
const job = jobs.get(id, caller)
|
|
768
785
|
/** @type {'cancellation-requested'|'already-finished'} */
|
|
@@ -844,8 +861,10 @@ export class ToolSurface {
|
|
|
844
861
|
* @returns {Promise<import('./registry.js').ServerRecord>}
|
|
845
862
|
*/
|
|
846
863
|
async #resolveServer(query) {
|
|
864
|
+
/** @type {import('./registry.js').ServerRecord} */
|
|
865
|
+
let server
|
|
847
866
|
try {
|
|
848
|
-
|
|
867
|
+
server = await this.#registry.resolve(String(query ?? ''))
|
|
849
868
|
} catch (error) {
|
|
850
869
|
const code = /** @type {{code?: string, message?: string}} */ (error).code
|
|
851
870
|
if (code === 'UNKNOWN_SERVER' || code === 'AMBIGUOUS_SERVER' || code === 'INVALID_SERVER') {
|
|
@@ -857,6 +876,19 @@ export class ToolSurface {
|
|
|
857
876
|
}
|
|
858
877
|
throw error
|
|
859
878
|
}
|
|
879
|
+
// The enable switch (doc §5.1). Every srv_* tool resolves its target here,
|
|
880
|
+
// so one check covers srv_exec, srv_connect, srv_policy_check and the job
|
|
881
|
+
// tools alike. A disabled server is still addressable by id/name/prefix —
|
|
882
|
+
// that is how the panel names it — so the check belongs after resolution,
|
|
883
|
+
// not inside it.
|
|
884
|
+
if (server.enabled === false) {
|
|
885
|
+
throw new ToolError(
|
|
886
|
+
'SERVER_DISABLED',
|
|
887
|
+
`server ${server.id} (${server.name}) is disabled.`,
|
|
888
|
+
'Enable it in the "服务器" panel of this plugin, then retry.',
|
|
889
|
+
)
|
|
890
|
+
}
|
|
891
|
+
return server
|
|
860
892
|
}
|
|
861
893
|
|
|
862
894
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-server-connector",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "DSH plugin: SSH execution for servers configured on this machine, with a built-in high-risk command policy (block / confirm / allow).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"lib",
|
|
19
19
|
"locale",
|
|
20
20
|
"icon.svg",
|
|
21
|
-
"cordis.patch.yml"
|
|
21
|
+
"cordis.patch.yml",
|
|
22
|
+
"CHANGELOG.md"
|
|
22
23
|
],
|
|
23
24
|
"icon": "./icon.svg",
|
|
24
25
|
"engines": {
|