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/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(this.#loaded ? this.#servers : [], {
417
- maxListedServers: this.#config.maxListedServers,
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
- return surface.servers.map((server) => ({
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
- const caller = surface.#ownerOf(undefined)
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
- const caller = surface.#ownerOf(undefined)
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
- return await this.#registry.resolve(String(query ?? ''))
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.0",
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": {