dsh-agent-plugins-market 0.8.0 → 0.8.2
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/README.md +2 -2
- package/README.zh.md +2 -2
- package/client/client.js +1594 -1732
- package/client/client.js.map +1 -1
- package/lib/application/agent-roles.js +53 -0
- package/lib/application/catalog-context.js +2 -2
- package/lib/application/catalog.js +4 -8
- package/lib/{runtime → application}/deadline.js +1 -1
- package/lib/application/details.js +3 -3
- package/lib/application/json-file.js +43 -0
- package/lib/{runtime → application/lsp}/lsp-direct-config.js +9 -7
- package/lib/application/lsp/lsp-server-state.js +17 -0
- package/lib/{runtime → application/lsp}/lsp-status.js +22 -7
- package/lib/{runtime → application/lsp}/profile-seam.js +1 -1
- package/lib/application/lsp-service.js +5 -5
- package/lib/{runtime → application/mcp}/mcp-backend.js +6 -21
- package/lib/{runtime → application/mcp}/mcp-config.js +4 -4
- package/lib/{runtime → application/mcp}/mcp-direct-config.js +8 -49
- package/lib/{runtime → application/mcp}/mcp-overrides.js +3 -5
- package/lib/{runtime → application/mcp}/mcp-status.js +13 -7
- package/lib/application/mcp-service.js +11 -35
- package/lib/application/panel-resources.js +48 -11
- package/lib/{runtime → application/panels}/user-hooks.js +2 -2
- package/lib/{runtime → application/panels}/user-store.js +2 -2
- package/lib/application/ports.js +1 -0
- package/lib/application/project-agent-roles.js +1 -1
- package/lib/{runtime → application}/server-config.js +7 -17
- package/lib/application/snapshot-cache.js +1 -1
- package/lib/application/source-store.js +3 -4
- package/lib/{runtime → application/state}/state-store.js +5 -7
- package/lib/{runtime → application/state}/storage-migration.js +4 -4
- package/lib/catalog/source-catalog.js +0 -4
- package/lib/contracts/lsp.js +1 -0
- package/lib/contracts/market.js +0 -1
- package/lib/contracts/mcp.js +1 -0
- package/lib/index.js +64 -34
- package/lib/routes.js +2 -9
- package/lib/runtime/{agent-role-router.js → agents/agent-role-router.js} +7 -53
- package/lib/runtime/{subagent-catalog.js → agents/subagent-catalog.js} +24 -16
- package/lib/runtime/core/context.js +31 -0
- package/lib/runtime/{reconciler.js → core/reconciler.js} +5 -5
- package/lib/runtime/host/failure-detail.js +58 -0
- package/lib/runtime/{feedback-tool.js → host/feedback-tool.js} +1 -1
- package/lib/runtime/host/host-locale.js +83 -0
- package/lib/runtime/{settings-namespace.js → host/settings-namespace.js} +44 -66
- package/lib/runtime/host/shell-path.js +196 -0
- package/lib/runtime/{tool-registry-observer.js → host/tool-registry-observer.js} +2 -1
- package/lib/runtime/{lsp-mounts.js → lsp/lsp-mounts.js} +69 -20
- package/lib/runtime/{mcp-client → mcp/bridge}/bridge.js +52 -22
- package/lib/runtime/{mcp-client → mcp/bridge}/connection.js +2 -2
- package/lib/runtime/{mcp-client → mcp/bridge}/oauth.js +1 -1
- package/lib/runtime/{mcp-client → mcp/bridge}/tools.js +2 -2
- package/lib/runtime/{mcp-auth-record.js → mcp/mcp-auth-record.js} +1 -1
- package/lib/runtime/{mcp-mounts.js → mcp/mcp-mounts.js} +14 -7
- package/lib/runtime/{user-commands.js → panels/user-commands.js} +3 -3
- package/lib/runtime/{user-panels.js → panels/user-panels.js} +2 -2
- package/lib/runtime/{commands-mounts.js → surfaces/commands-mounts.js} +7 -7
- package/lib/runtime/{hooks-mounts.js → surfaces/hooks-mounts.js} +2 -2
- package/lib/runtime/{project-runtime.js → surfaces/project-runtime.js} +5 -5
- package/lib/runtime/{skills-provider.js → surfaces/skills-provider.js} +4 -4
- package/lib/types/application/agent-roles.d.ts +30 -0
- package/lib/types/application/catalog.d.ts +4 -7
- package/lib/types/application/details.d.ts +1 -1
- package/lib/types/application/json-file.d.ts +12 -0
- package/lib/types/{runtime → application/lsp}/lsp-direct-config.d.ts +1 -1
- package/lib/types/{runtime → application/lsp}/lsp-status.d.ts +3 -3
- package/lib/types/{runtime → application/mcp}/mcp-backend.d.ts +24 -22
- package/lib/types/{runtime/mcp-client/config.d.ts → application/mcp/mcp-bridge-config.d.ts} +11 -1
- package/lib/types/{runtime → application/mcp}/mcp-config.d.ts +6 -7
- package/lib/types/application/mcp/mcp-direct-config.d.ts +17 -0
- package/lib/types/{runtime → application/mcp}/mcp-overrides.d.ts +1 -1
- package/lib/types/{runtime → application/mcp}/mcp-status.d.ts +7 -7
- package/lib/types/application/mcp-service.d.ts +4 -11
- package/lib/types/application/panel-resources.d.ts +9 -2
- package/lib/types/{runtime → application/panels}/user-hooks.d.ts +1 -1
- package/lib/types/{runtime → application/panels}/user-store.d.ts +1 -1
- package/lib/types/application/ports.d.ts +17 -3
- package/lib/types/application/project-agent-roles.d.ts +1 -1
- package/lib/types/application/queries.d.ts +3 -6
- package/lib/types/{runtime → application}/regions.d.ts +1 -1
- package/lib/types/{runtime → application/state}/state-store.d.ts +2 -2
- package/lib/types/catalog/source-catalog.d.ts +0 -2
- package/lib/types/contracts/lsp-status.d.ts +6 -0
- package/lib/types/contracts/lsp.d.ts +16 -0
- package/lib/types/contracts/market.d.ts +1 -4
- package/lib/types/contracts/mcp-status.d.ts +21 -2
- package/lib/types/contracts/mcp.d.ts +20 -0
- package/lib/types/index.d.ts +87 -1
- package/lib/types/model/types.d.ts +0 -14
- package/lib/types/runtime/{agent-role-router.d.ts → agents/agent-role-router.d.ts} +3 -28
- package/lib/types/runtime/{subagent-catalog.d.ts → agents/subagent-catalog.d.ts} +6 -6
- package/lib/types/runtime/core/context.d.ts +14 -0
- package/lib/types/runtime/{reconcile-scheduler.d.ts → core/reconcile-scheduler.d.ts} +2 -2
- package/lib/types/runtime/{reconciler.d.ts → core/reconciler.d.ts} +8 -8
- package/lib/types/runtime/host/failure-detail.d.ts +6 -0
- package/lib/types/runtime/host/host-locale.d.ts +66 -0
- package/lib/types/runtime/{model-catalog.d.ts → host/model-catalog.d.ts} +1 -1
- package/lib/types/runtime/{settings-namespace.d.ts → host/settings-namespace.d.ts} +42 -32
- package/lib/types/runtime/host/shell-path.d.ts +70 -0
- package/lib/types/runtime/{lsp-mounts.d.ts → lsp/lsp-mounts.d.ts} +17 -11
- package/lib/types/runtime/{mcp-client → mcp/bridge}/bridge.d.ts +1 -1
- package/lib/types/runtime/{mcp-client → mcp/bridge}/connection.d.ts +3 -11
- package/lib/types/runtime/{mcp-client → mcp/bridge}/host-contract.d.ts +1 -1
- package/lib/types/runtime/{mcp-client → mcp/bridge}/oauth.d.ts +1 -1
- package/lib/types/runtime/{mcp-client → mcp/bridge}/transport.d.ts +1 -1
- package/lib/types/runtime/{mcp-auth-record.d.ts → mcp/mcp-auth-record.d.ts} +1 -1
- package/lib/types/runtime/{mcp-credentials.d.ts → mcp/mcp-credentials.d.ts} +1 -1
- package/lib/types/runtime/{mcp-mounts.d.ts → mcp/mcp-mounts.d.ts} +5 -11
- package/lib/types/runtime/{user-commands.d.ts → panels/user-commands.d.ts} +1 -1
- package/lib/types/runtime/{user-panels.d.ts → panels/user-panels.d.ts} +1 -1
- package/lib/types/runtime/{commands-mounts.d.ts → surfaces/commands-mounts.d.ts} +2 -2
- package/lib/types/runtime/{hooks-mounts.d.ts → surfaces/hooks-mounts.d.ts} +1 -1
- package/lib/types/runtime/{project-runtime.d.ts → surfaces/project-runtime.d.ts} +3 -3
- package/lib/types/runtime/{skills-provider.d.ts → surfaces/skills-provider.d.ts} +1 -1
- package/package.json +39 -33
- package/lib/runtime/host-locale.js +0 -89
- package/lib/runtime/legacy-root-migration.js +0 -12
- package/lib/runtime/lsp-server-state.js +0 -19
- package/lib/runtime/mcp-client/json-schema-subset.js +0 -181
- package/lib/types/runtime/host-locale.d.ts +0 -31
- package/lib/types/runtime/legacy-root-migration.d.ts +0 -2
- package/lib/types/runtime/mcp-client/json-schema-subset.d.ts +0 -58
- package/lib/types/runtime/mcp-direct-config.d.ts +0 -39
- /package/lib/{runtime/mcp-client/config.js → application/mcp/mcp-bridge-config.js} +0 -0
- /package/lib/{runtime → application/mcp}/mcp-redaction.js +0 -0
- /package/lib/{runtime → application}/regions.js +0 -0
- /package/lib/runtime/{agent-role-names.js → agents/agent-role-names.js} +0 -0
- /package/lib/runtime/{mount-lifecycle.js → core/mount-lifecycle.js} +0 -0
- /package/lib/runtime/{reconcile-scheduler.js → core/reconcile-scheduler.js} +0 -0
- /package/lib/runtime/{source-auto-update.js → core/source-auto-update.js} +0 -0
- /package/lib/runtime/{timer-seat.js → core/timer-seat.js} +0 -0
- /package/lib/runtime/{model-catalog.js → host/model-catalog.js} +0 -0
- /package/lib/runtime/{plugin-message-source.js → host/plugin-message-source.js} +0 -0
- /package/lib/runtime/{mcp-client → mcp/bridge}/host-contract.js +0 -0
- /package/lib/runtime/{mcp-client → mcp/bridge}/host-seams.js +0 -0
- /package/lib/runtime/{mcp-client → mcp/bridge}/plugin-identity.js +0 -0
- /package/lib/runtime/{mcp-client → mcp/bridge}/projection.js +0 -0
- /package/lib/runtime/{mcp-client → mcp/bridge}/transport.js +0 -0
- /package/lib/runtime/{mcp-credentials.js → mcp/mcp-credentials.js} +0 -0
- /package/lib/runtime/{dynamic-context.js → surfaces/dynamic-context.js} +0 -0
- /package/lib/types/{runtime → application}/deadline.d.ts +0 -0
- /package/lib/types/{runtime → application/lsp}/lsp-server-state.d.ts +0 -0
- /package/lib/types/{runtime → application/lsp}/profile-seam.d.ts +0 -0
- /package/lib/types/{runtime → application/mcp}/mcp-redaction.d.ts +0 -0
- /package/lib/types/{runtime → application}/server-config.d.ts +0 -0
- /package/lib/types/{runtime → application/state}/storage-migration.d.ts +0 -0
- /package/lib/types/runtime/{agent-role-names.d.ts → agents/agent-role-names.d.ts} +0 -0
- /package/lib/types/runtime/{mount-lifecycle.d.ts → core/mount-lifecycle.d.ts} +0 -0
- /package/lib/types/runtime/{source-auto-update.d.ts → core/source-auto-update.d.ts} +0 -0
- /package/lib/types/runtime/{timer-seat.d.ts → core/timer-seat.d.ts} +0 -0
- /package/lib/types/runtime/{feedback-tool.d.ts → host/feedback-tool.d.ts} +0 -0
- /package/lib/types/runtime/{plugin-message-source.d.ts → host/plugin-message-source.d.ts} +0 -0
- /package/lib/types/runtime/{tool-registry-observer.d.ts → host/tool-registry-observer.d.ts} +0 -0
- /package/lib/types/runtime/{mcp-client → mcp/bridge}/host-seams.d.ts +0 -0
- /package/lib/types/runtime/{mcp-client → mcp/bridge}/plugin-identity.d.ts +0 -0
- /package/lib/types/runtime/{mcp-client → mcp/bridge}/projection.d.ts +0 -0
- /package/lib/types/runtime/{mcp-client → mcp/bridge}/tools.d.ts +0 -0
- /package/lib/types/runtime/{dynamic-context.d.ts → surfaces/dynamic-context.d.ts} +0 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent-role card parsing: the frontmatter grammar and the policy it yields.
|
|
3
|
+
* Pure text processing shared by the project-role projection and the runtime
|
|
4
|
+
* delegation router; the executing half stays in runtime.
|
|
5
|
+
* @module application/agent-roles
|
|
6
|
+
*/
|
|
7
|
+
import { parse as parseYaml } from 'yaml';
|
|
8
|
+
function optionalText(value, key) {
|
|
9
|
+
if (value === undefined)
|
|
10
|
+
return undefined;
|
|
11
|
+
if (typeof value !== 'string' || value.trim() === '')
|
|
12
|
+
throw new Error(`agent metadata ${key} must be a non-empty string`);
|
|
13
|
+
return value.trim();
|
|
14
|
+
}
|
|
15
|
+
/** Strict YAML parsing prevents malformed routing metadata from silently inheriting wider privileges. */
|
|
16
|
+
export function parseAgentRole(text) {
|
|
17
|
+
const match = /^(?:\uFEFF)?---\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/.exec(text);
|
|
18
|
+
if (match === null) {
|
|
19
|
+
if (/^(?:\uFEFF)?---(?:\r?\n|$)/.test(text))
|
|
20
|
+
throw new Error('agent frontmatter is not closed');
|
|
21
|
+
return { content: text, disabled: false };
|
|
22
|
+
}
|
|
23
|
+
// A matched block always carries its body; the check keeps the type honest.
|
|
24
|
+
const frontmatter = match[1];
|
|
25
|
+
if (frontmatter === undefined)
|
|
26
|
+
throw new Error('agent frontmatter is not closed');
|
|
27
|
+
const parsed = parseYaml(frontmatter);
|
|
28
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
29
|
+
throw new Error('agent frontmatter must be a YAML object');
|
|
30
|
+
const metadata = parsed;
|
|
31
|
+
if (metadata.disabled !== undefined && typeof metadata.disabled !== 'boolean')
|
|
32
|
+
throw new Error('agent metadata disabled must be a boolean');
|
|
33
|
+
const model = optionalText(metadata.model, 'model');
|
|
34
|
+
const provider = optionalText(metadata.provider, 'provider');
|
|
35
|
+
const effort = optionalText(metadata.reasoning_effort, 'reasoning_effort');
|
|
36
|
+
const camelEffort = optionalText(metadata.reasoningEffort, 'reasoningEffort');
|
|
37
|
+
if (effort !== undefined && camelEffort !== undefined && effort !== camelEffort)
|
|
38
|
+
throw new Error('agent metadata reasoning_effort and reasoningEffort conflict');
|
|
39
|
+
const reasoningEffort = effort ?? camelEffort;
|
|
40
|
+
const title = optionalText(metadata.name, 'name');
|
|
41
|
+
const description = optionalText(metadata.description, 'description');
|
|
42
|
+
if (model === 'inherit' && provider !== undefined)
|
|
43
|
+
throw new Error('agent model inherit cannot specify a provider');
|
|
44
|
+
return {
|
|
45
|
+
content: text.slice(match[0].length),
|
|
46
|
+
disabled: metadata.disabled === true,
|
|
47
|
+
...(model === undefined || model === 'inherit' ? {} : { model }),
|
|
48
|
+
...(provider === undefined ? {} : { provider }),
|
|
49
|
+
...(reasoningEffort === undefined ? {} : { reasoningEffort }),
|
|
50
|
+
...(title === undefined ? {} : { title }),
|
|
51
|
+
...(description === undefined ? {} : { description })
|
|
52
|
+
};
|
|
53
|
+
}
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
import { join } from 'node:path';
|
|
11
11
|
import { MARKET_SETTINGS_DEFAULTS } from '../contracts/settings.js';
|
|
12
12
|
import { STATE_FILE_NAME } from '../catalog/paths.js';
|
|
13
|
-
import { settlesWithin } from '
|
|
14
|
-
import { EMPTY_STATE, loadState, saveState } from '
|
|
13
|
+
import { settlesWithin } from './deadline.js';
|
|
14
|
+
import { EMPTY_STATE, loadState, saveState } from './state/state-store.js';
|
|
15
15
|
import { effectiveSurfaces } from '../model/types.js';
|
|
16
16
|
import { SCAN_CACHE_TTL_MS, SnapshotCache } from './snapshot-cache.js';
|
|
17
17
|
/** Key of one install entry in the persisted `installed` record. */
|
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
* know which collaborator owns a given invariant.
|
|
11
11
|
*/
|
|
12
12
|
import { qualifiedSuiteId } from '../catalog/paths.js';
|
|
13
|
-
import { loadUserMcpSuite } from '
|
|
14
|
-
import { loadSuiteOverrides } from '
|
|
15
|
-
import { loadUserHooksSuite } from '
|
|
16
|
-
import { applyLspOverrides } from '
|
|
13
|
+
import { loadUserMcpSuite } from './mcp/mcp-direct-config.js';
|
|
14
|
+
import { loadSuiteOverrides } from './mcp/mcp-overrides.js';
|
|
15
|
+
import { loadUserHooksSuite } from './panels/user-hooks.js';
|
|
16
|
+
import { applyLspOverrides } from './server-config.js';
|
|
17
17
|
import { buildSuiteDetail, readSkillContent } from './details.js';
|
|
18
18
|
import { CatalogContext } from './catalog-context.js';
|
|
19
19
|
import { InstallStore } from './install-store.js';
|
|
@@ -233,10 +233,6 @@ export class Catalog {
|
|
|
233
233
|
async addMcpServer(name, server) {
|
|
234
234
|
await this.mcp.addServer(name, server);
|
|
235
235
|
}
|
|
236
|
-
/** Import several pasted services in one write; each entry reports its own outcome. */
|
|
237
|
-
async importMcpServers(servers, overwrite) {
|
|
238
|
-
return this.mcp.importServers(servers, overwrite);
|
|
239
|
-
}
|
|
240
236
|
/** Read effective service configuration without exposing credential literals. */
|
|
241
237
|
async serverConfig(kind, id) {
|
|
242
238
|
return this.mcp.serverConfig(kind, id);
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* only notifies. This module adds the one thing that primitive does not answer
|
|
12
12
|
* and these callers need: whether the work settled at all.
|
|
13
13
|
*
|
|
14
|
-
* @module
|
|
14
|
+
* @module application/deadline
|
|
15
15
|
*/
|
|
16
16
|
import { deadline } from '@deepseek-ai/dsh-timeout';
|
|
17
17
|
/** Identifies this plugin's bounded waits inside a timeout reason. */
|
|
@@ -3,9 +3,9 @@ import { readFile, stat } from 'node:fs/promises';
|
|
|
3
3
|
import { parse as parseYaml } from 'yaml';
|
|
4
4
|
import { discoverLspEntries } from '../catalog/surfaces.js';
|
|
5
5
|
import { defaultMarkdownResources, resourceText } from '../catalog/component-files.js';
|
|
6
|
-
import { credentialRefsInServer } from '
|
|
7
|
-
import { redactMcpConfig, redactMcpOverrides } from '
|
|
8
|
-
import { applyOverride } from '
|
|
6
|
+
import { credentialRefsInServer } from './mcp/mcp-config.js';
|
|
7
|
+
import { redactMcpConfig, redactMcpOverrides } from './mcp/mcp-redaction.js';
|
|
8
|
+
import { applyOverride } from './mcp/mcp-overrides.js';
|
|
9
9
|
import { effectiveSurfaces } from '../model/types.js';
|
|
10
10
|
/** The checkout's last modification, or null when the path cannot be read. */
|
|
11
11
|
async function rootModifiedAt(root) {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one JSON persistence loop every plugin-owned document runs: read with
|
|
3
|
+
* ENOENT tolerance, validate through a caller hook, and write back through the
|
|
4
|
+
* harness atomic-write helper with the private-file modes. Files here are
|
|
5
|
+
* plugin state under `$DSH_HOME`, written by the host process only; a caller
|
|
6
|
+
* needing shared or user-authored content keeps its own rules.
|
|
7
|
+
*
|
|
8
|
+
* The read resolves `undefined` when the file does not exist or parses to
|
|
9
|
+
* nothing usable, and rethrows anything else — a permission problem must
|
|
10
|
+
* surface, not read as an empty document. What counts as usable is the
|
|
11
|
+
* caller's `parse`: it receives the parsed value and returns the typed
|
|
12
|
+
* document, so each store keeps its own tolerance policy (fail-closed to a
|
|
13
|
+
* default, or reject) instead of sharing one.
|
|
14
|
+
* @module application/json-file
|
|
15
|
+
*/
|
|
16
|
+
import { readFile } from 'node:fs/promises';
|
|
17
|
+
import { writeFileAtomic } from '@deepseek-ai/dsh-atomic-write';
|
|
18
|
+
/**
|
|
19
|
+
* Read one JSON document. Resolves `undefined` when the file is absent or its
|
|
20
|
+
* text is empty; a `parse` that wants an absent file to read as a default
|
|
21
|
+
* handles `undefined` itself.
|
|
22
|
+
*/
|
|
23
|
+
export async function readJsonFile(path) {
|
|
24
|
+
try {
|
|
25
|
+
const text = await readFile(path, 'utf8');
|
|
26
|
+
if (text.trim() === '')
|
|
27
|
+
return undefined;
|
|
28
|
+
return JSON.parse(text);
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
if (error.code === 'ENOENT')
|
|
32
|
+
return undefined;
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Write one JSON document atomically: two-space indent, trailing newline, and
|
|
38
|
+
* the private-file modes the plugin's state files carry (0o600 file, 0o700
|
|
39
|
+
* directory).
|
|
40
|
+
*/
|
|
41
|
+
export async function writeJsonDocument(path, document) {
|
|
42
|
+
await writeFileAtomic(path, `${JSON.stringify(document, null, 2)}\n`, { mode: 0o600, dirMode: 0o700 });
|
|
43
|
+
}
|
|
@@ -8,10 +8,9 @@
|
|
|
8
8
|
* `lsp-spec` rules as suite declarations; a broken file degrades to no direct
|
|
9
9
|
* servers plus a diagnostic, never a thrown discovery.
|
|
10
10
|
*/
|
|
11
|
-
import { readFile } from 'node:fs/promises';
|
|
12
11
|
import { join } from 'node:path';
|
|
13
|
-
import {
|
|
14
|
-
import { parseLspServers } from '
|
|
12
|
+
import { readJsonFile, writeJsonDocument } from '../json-file.js';
|
|
13
|
+
import { parseLspServers } from '../../catalog/lsp-spec.js';
|
|
15
14
|
/** The direct LSP server configuration file path. */
|
|
16
15
|
export function lspServersPath(agentsRoot) {
|
|
17
16
|
return join(agentsRoot, 'lsp.json');
|
|
@@ -20,11 +19,15 @@ export function lspServersPath(agentsRoot) {
|
|
|
20
19
|
export async function loadLspServers(agentsRoot) {
|
|
21
20
|
let raw;
|
|
22
21
|
try {
|
|
23
|
-
raw =
|
|
22
|
+
raw = await readJsonFile(lspServersPath(agentsRoot));
|
|
24
23
|
}
|
|
25
24
|
catch (error) {
|
|
26
|
-
return { servers: {}, errors:
|
|
25
|
+
return { servers: {}, errors: [`lsp.json: ${error instanceof Error ? error.message : String(error)}`] };
|
|
27
26
|
}
|
|
27
|
+
// An absent file is an empty table, not a broken one; only a present file
|
|
28
|
+
// with a wrong shape carries a diagnostic.
|
|
29
|
+
if (raw === undefined)
|
|
30
|
+
return { servers: {}, errors: [] };
|
|
28
31
|
const errors = [];
|
|
29
32
|
const table = raw?.['lspServers'];
|
|
30
33
|
if (table === undefined || table === null || Array.isArray(table) || typeof table !== 'object')
|
|
@@ -40,8 +43,7 @@ export async function saveLspServers(agentsRoot, raw) {
|
|
|
40
43
|
if (errors.length > 0) {
|
|
41
44
|
throw new Error(`invalid lspServers: ${errors[0]}`);
|
|
42
45
|
}
|
|
43
|
-
const path = lspServersPath(agentsRoot);
|
|
44
46
|
const persisted = Object.fromEntries(Object.entries(servers).map(([name, { key: _key, ...config }]) => [name, config]));
|
|
45
|
-
await
|
|
47
|
+
await writeJsonDocument(lspServersPath(agentsRoot), { lspServers: persisted });
|
|
46
48
|
return { servers };
|
|
47
49
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { readJsonFile, writeJsonDocument } from '../json-file.js';
|
|
3
|
+
export function lspServerStatePath(dataRoot) {
|
|
4
|
+
return join(dataRoot, 'lsp-server-state.json');
|
|
5
|
+
}
|
|
6
|
+
export async function loadDisabledLspServers(dataRoot) {
|
|
7
|
+
try {
|
|
8
|
+
const value = (await readJsonFile(lspServerStatePath(dataRoot)));
|
|
9
|
+
return new Set(Array.isArray(value?.disabled) ? value.disabled.filter((id) => typeof id === 'string') : []);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return new Set();
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export async function saveDisabledLspServers(dataRoot, disabled) {
|
|
16
|
+
await writeJsonDocument(lspServerStatePath(dataRoot), { disabled: [...new Set(disabled)].sort() });
|
|
17
|
+
}
|
|
@@ -1,6 +1,16 @@
|
|
|
1
|
-
import { qualifiedSuiteId } from '
|
|
1
|
+
import { qualifiedSuiteId } from '../../catalog/paths.js';
|
|
2
2
|
/** Sentinel suite id for user-configured (non-suite) LSP servers. */
|
|
3
3
|
export const DIRECT_LSP_SUITE_ID = 'direct';
|
|
4
|
+
/** The fields one diagnostic contributes to its status row. */
|
|
5
|
+
function diagnosticReport(diagnostic) {
|
|
6
|
+
if (diagnostic === undefined)
|
|
7
|
+
return {};
|
|
8
|
+
return {
|
|
9
|
+
reason: diagnostic.reason,
|
|
10
|
+
...(diagnostic.code === undefined ? {} : { code: diagnostic.code }),
|
|
11
|
+
...(diagnostic.causes === undefined ? {} : { causes: diagnostic.causes })
|
|
12
|
+
};
|
|
13
|
+
}
|
|
4
14
|
/**
|
|
5
15
|
* Derive one row's state from its suite's diagnostic and the live-mount flag.
|
|
6
16
|
* Shared by suite rows and direct rows so both kinds report identically.
|
|
@@ -8,15 +18,16 @@ export const DIRECT_LSP_SUITE_ID = 'direct';
|
|
|
8
18
|
function deriveState(disabled, diagnostic, anyLive) {
|
|
9
19
|
if (disabled)
|
|
10
20
|
return { state: 'disabled' };
|
|
21
|
+
const reported = diagnosticReport(diagnostic);
|
|
11
22
|
if (diagnostic?.code === 'host-missing')
|
|
12
|
-
return { state: 'host-missing',
|
|
23
|
+
return { state: 'host-missing', ...reported };
|
|
13
24
|
if (diagnostic?.code === 'seam-conflict')
|
|
14
|
-
return { state: 'conflict',
|
|
25
|
+
return { state: 'conflict', ...reported };
|
|
15
26
|
if (diagnostic?.code === 'mount-failed' || diagnostic?.code === 'unmount-failed') {
|
|
16
|
-
return { state: 'failed',
|
|
27
|
+
return { state: 'failed', ...reported, retryable: true };
|
|
17
28
|
}
|
|
18
29
|
if (diagnostic !== undefined)
|
|
19
|
-
return { state: 'failed',
|
|
30
|
+
return { state: 'failed', ...reported };
|
|
20
31
|
if (anyLive)
|
|
21
32
|
return { state: 'mounted' };
|
|
22
33
|
// A declaration with neither a diagnostic nor a live mount is mid-flight:
|
|
@@ -42,7 +53,7 @@ export function buildLspStatus(suites, registry, direct = { servers: {}, errors:
|
|
|
42
53
|
const suiteKey = qualifiedSuiteId(suite.sourceId, suite.id);
|
|
43
54
|
const disabled = suite.activeSurfaces.lsp === false;
|
|
44
55
|
const diagnostic = diagnostics.get(suiteKey);
|
|
45
|
-
const { state, reason, retryable } = deriveState(disabled, diagnostic, anyLive);
|
|
56
|
+
const { state, reason, code, causes, retryable } = deriveState(disabled, diagnostic, anyLive);
|
|
46
57
|
if (state === 'mounted')
|
|
47
58
|
mountedSuiteIds.add(suiteKey);
|
|
48
59
|
for (const spec of Object.values(suite.lsp.servers)) {
|
|
@@ -58,6 +69,8 @@ export function buildLspStatus(suites, registry, direct = { servers: {}, errors:
|
|
|
58
69
|
extensions: spec.extensionToLanguage,
|
|
59
70
|
state: disabled || disabledServers.has(`${suiteKey}/${spec.key}`) ? 'disabled' : state,
|
|
60
71
|
...(reason === undefined ? {} : { reason }),
|
|
72
|
+
...(code === undefined ? {} : { code }),
|
|
73
|
+
...(causes === undefined ? {} : { causes }),
|
|
61
74
|
...(retryable === undefined ? {} : { retryable })
|
|
62
75
|
});
|
|
63
76
|
}
|
|
@@ -65,7 +78,7 @@ export function buildLspStatus(suites, registry, direct = { servers: {}, errors:
|
|
|
65
78
|
// Direct user-configured rows: no install state or surface toggle of their
|
|
66
79
|
// own — configured means on; the mount diagnostic decides the state.
|
|
67
80
|
const directDiagnostic = diagnostics.get(DIRECT_LSP_SUITE_ID);
|
|
68
|
-
const { state: directState, reason: directReason, retryable: directRetryable } = deriveState(false, directDiagnostic, anyLive);
|
|
81
|
+
const { state: directState, reason: directReason, code: directCode, causes: directCauses, retryable: directRetryable } = deriveState(false, directDiagnostic, anyLive);
|
|
69
82
|
if (directState === 'mounted')
|
|
70
83
|
mountedSuiteIds.add(DIRECT_LSP_SUITE_ID);
|
|
71
84
|
for (const spec of Object.values(direct.servers)) {
|
|
@@ -81,6 +94,8 @@ export function buildLspStatus(suites, registry, direct = { servers: {}, errors:
|
|
|
81
94
|
extensions: spec.extensionToLanguage,
|
|
82
95
|
state: disabledServers.has(`${DIRECT_LSP_SUITE_ID}/${spec.key}`) ? 'disabled' : directState,
|
|
83
96
|
...(directReason === undefined ? {} : { reason: directReason }),
|
|
97
|
+
...(directCode === undefined ? {} : { code: directCode }),
|
|
98
|
+
...(directCauses === undefined ? {} : { causes: directCauses }),
|
|
84
99
|
...(directRetryable === undefined ? {} : { retryable: directRetryable })
|
|
85
100
|
});
|
|
86
101
|
}
|
|
@@ -28,7 +28,7 @@ import { readdir, readFile, stat } from 'node:fs/promises';
|
|
|
28
28
|
import { join } from 'node:path';
|
|
29
29
|
import { isMap, isNode, isScalar, isSeq, parseDocument } from 'yaml';
|
|
30
30
|
import { writeFileAtomic } from '@deepseek-ai/dsh-atomic-write';
|
|
31
|
-
import { resolveDshHome } from '
|
|
31
|
+
import { resolveDshHome } from '../../catalog/paths.js';
|
|
32
32
|
/** Every package the plugin provisions for LSP, in declaration order. */
|
|
33
33
|
export const LSP_PACKAGE_SPECIFIERS = ['@deepseek-ai/dsh-lsp', '@deepseek-ai/dsh-lsp-stdio', '@deepseek-ai/dsh-tool-lsp'];
|
|
34
34
|
/**
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { loadLspServers, saveLspServers } from '
|
|
2
|
-
import { loadDisabledLspServers, saveDisabledLspServers } from '
|
|
3
|
-
import { buildLspStatus } from '
|
|
4
|
-
import { describeLegacySeam, findLegacyLspSeams, migrateLegacyLspSeam } from '
|
|
5
|
-
import { applyLspOverrides, lspConfig, validateServerLsp } from '
|
|
1
|
+
import { loadLspServers, saveLspServers } from './lsp/lsp-direct-config.js';
|
|
2
|
+
import { loadDisabledLspServers, saveDisabledLspServers } from './lsp/lsp-server-state.js';
|
|
3
|
+
import { buildLspStatus } from './lsp/lsp-status.js';
|
|
4
|
+
import { describeLegacySeam, findLegacyLspSeams, migrateLegacyLspSeam } from './lsp/profile-seam.js';
|
|
5
|
+
import { applyLspOverrides, lspConfig, validateServerLsp } from './server-config.js';
|
|
6
6
|
export class LspService {
|
|
7
7
|
context;
|
|
8
8
|
ports;
|
|
@@ -8,43 +8,28 @@
|
|
|
8
8
|
* namespace (see `contracts/settings.ts`) registered by the plugin's node half:
|
|
9
9
|
* the registration is what makes the host 插件配置 tab serve our card, the
|
|
10
10
|
* client card binds it for state, and the node half watches it to remount
|
|
11
|
-
* servers when the switch flips.
|
|
12
|
-
* one-time migration from the earlier data-root `settings.json` choice.
|
|
11
|
+
* servers when the switch flips.
|
|
13
12
|
*
|
|
14
|
-
* @module
|
|
13
|
+
* @module application/mcp-backend
|
|
15
14
|
*/
|
|
16
15
|
import { readFile } from 'node:fs/promises';
|
|
17
16
|
import { createRequire } from 'node:module';
|
|
18
|
-
import { join } from 'node:path';
|
|
19
17
|
import z from '@deepseek-ai/schemastery';
|
|
20
|
-
import { MARKET_SETTINGS_DEFAULTS } from '
|
|
18
|
+
import { MARKET_SETTINGS_DEFAULTS } from '../../contracts/settings.js';
|
|
21
19
|
/**
|
|
22
20
|
* Schema of the market settings namespace: the switches this plugin's config
|
|
23
21
|
* card serves, with the defaults declared to the host. The values themselves
|
|
24
22
|
* belong to {@link MARKET_SETTINGS_DEFAULTS}; this schema states them, it does
|
|
25
23
|
* not own them.
|
|
26
24
|
*/
|
|
27
|
-
export const
|
|
25
|
+
export const MarketSettingsFields = {
|
|
28
26
|
mcpEnhanced: z.boolean().default(MARKET_SETTINGS_DEFAULTS.mcpEnhanced),
|
|
29
27
|
scanProjectLayouts: z.boolean().default(MARKET_SETTINGS_DEFAULTS.scanProjectLayouts),
|
|
30
28
|
downloadRegion: z.union([z.const('auto'), z.const('global'), z.const('china')]).default(MARKET_SETTINGS_DEFAULTS.downloadRegion),
|
|
31
29
|
feedbackEnabled: z.boolean().default(MARKET_SETTINGS_DEFAULTS.feedbackEnabled),
|
|
32
30
|
autoUpdateSources: z.boolean().default(MARKET_SETTINGS_DEFAULTS.autoUpdateSources)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function marketSettingsPath(dataRoot) {
|
|
36
|
-
return join(dataRoot, 'settings.json');
|
|
37
|
-
}
|
|
38
|
-
/** Read the legacy persisted backend; absent or invalid values read as the default. */
|
|
39
|
-
export async function readMcpBackend(dataRoot) {
|
|
40
|
-
try {
|
|
41
|
-
const parsed = JSON.parse(await readFile(marketSettingsPath(dataRoot), 'utf8'));
|
|
42
|
-
return parsed.mcpBackend === 'host' ? 'host' : 'builtin';
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
return 'builtin';
|
|
46
|
-
}
|
|
47
|
-
}
|
|
31
|
+
};
|
|
32
|
+
export const MarketSettingsSchema = z.object(MarketSettingsFields);
|
|
48
33
|
/**
|
|
49
34
|
* Probe whether the host's `dsh-mcp-client` is resolvable from this plugin's
|
|
50
35
|
* module context, and at which version. Best effort: any failure reads as
|
|
@@ -20,11 +20,11 @@
|
|
|
20
20
|
* server instead of becoming empty strings.
|
|
21
21
|
*/
|
|
22
22
|
import { createHash } from 'node:crypto';
|
|
23
|
-
import { DEFAULT_STARTUP_TIMEOUT_MS, DEFAULT_TOOL_CALL_TIMEOUT_MS } from './mcp-
|
|
24
|
-
import { resolveCwd } from '
|
|
25
|
-
import { qualifiedSuiteId, suiteDataDir } from '
|
|
23
|
+
import { DEFAULT_STARTUP_TIMEOUT_MS, DEFAULT_TOOL_CALL_TIMEOUT_MS } from './mcp-bridge-config.js';
|
|
24
|
+
import { resolveCwd } from '../../catalog/validate.js';
|
|
25
|
+
import { qualifiedSuiteId, suiteDataDir } from '../../catalog/paths.js';
|
|
26
26
|
import { applyOverride } from './mcp-overrides.js';
|
|
27
|
-
import { PLUGIN_ROOT_VARIABLES, PLUGIN_DATA_VARIABLES } from '
|
|
27
|
+
import { PLUGIN_ROOT_VARIABLES, PLUGIN_DATA_VARIABLES } from '../../model/layouts.js';
|
|
28
28
|
/** The max length the bridge accepts for a serverName. */
|
|
29
29
|
const SERVER_NAME_MAX = 32;
|
|
30
30
|
const PLACEHOLDER = /\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}/g;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { readFile } from 'node:fs/promises';
|
|
2
1
|
import { join } from 'node:path';
|
|
3
|
-
import {
|
|
4
|
-
import { MCP_SCHEMA_ID, validateMcpJson } from '
|
|
5
|
-
import { effectiveSurfaces } from '
|
|
2
|
+
import { readJsonFile, writeJsonDocument } from '../json-file.js';
|
|
3
|
+
import { MCP_SCHEMA_ID, validateMcpJson } from '../../catalog/validate.js';
|
|
4
|
+
import { effectiveSurfaces } from '../../model/types.js';
|
|
6
5
|
export const USER_MCP_SOURCE = '@user-mcp';
|
|
7
6
|
export const USER_MCP_SUITE = 'user-mcp';
|
|
8
7
|
/**
|
|
@@ -17,11 +16,12 @@ export async function loadUserMcpSuite(agentsRoot) {
|
|
|
17
16
|
let mcp = { schema: MCP_SCHEMA_ID, servers: {} };
|
|
18
17
|
const errors = [];
|
|
19
18
|
try {
|
|
20
|
-
|
|
19
|
+
const raw = await readJsonFile(path);
|
|
20
|
+
if (raw !== undefined)
|
|
21
|
+
mcp = await validateUserMcp(agentsRoot, raw);
|
|
21
22
|
}
|
|
22
23
|
catch (error) {
|
|
23
|
-
|
|
24
|
-
errors.push(`mcp.json: ${error instanceof Error ? error.message : String(error)}`);
|
|
24
|
+
errors.push(`mcp.json: ${error instanceof Error ? error.message : String(error)}`);
|
|
25
25
|
}
|
|
26
26
|
return {
|
|
27
27
|
sourceId: USER_MCP_SOURCE,
|
|
@@ -76,50 +76,9 @@ export async function addUserMcpServer(agentsRoot, name, server) {
|
|
|
76
76
|
throw new Error(`MCP server "${name}" already exists`);
|
|
77
77
|
const document = { $schema: MCP_SCHEMA_ID, mcpServers: { ...suite.mcp.servers, [name]: server } };
|
|
78
78
|
await validateUserMcp(agentsRoot, document);
|
|
79
|
-
|
|
80
|
-
await writeFileAtomic(path, `${JSON.stringify(document, null, 2)}\n`, { mode: 0o600, dirMode: 0o700 });
|
|
79
|
+
await writeJsonDocument(userMcpPath(agentsRoot), document);
|
|
81
80
|
}
|
|
82
81
|
/** The user's hand-written MCP declaration file: `<agentsRoot>/mcp.json`. */
|
|
83
82
|
export function userMcpPath(agentsRoot) {
|
|
84
83
|
return join(agentsRoot, 'mcp.json');
|
|
85
84
|
}
|
|
86
|
-
const SERVER_NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/;
|
|
87
|
-
/**
|
|
88
|
-
* Import several pasted servers in one write.
|
|
89
|
-
*
|
|
90
|
-
* Every entry is judged on its own so one bad definition cannot take the rest
|
|
91
|
-
* of the paste with it, and an existing name is kept unless the caller asked to
|
|
92
|
-
* overwrite. The file is written once, after every entry has been checked, so a
|
|
93
|
-
* rejected entry never lands half-written beside the accepted ones.
|
|
94
|
-
*/
|
|
95
|
-
export async function importUserMcpServers(agentsRoot, entries, overwrite = false) {
|
|
96
|
-
const suite = await loadUserMcpSuite(agentsRoot);
|
|
97
|
-
if (suite.errors.length > 0)
|
|
98
|
-
throw new Error(suite.errors.join('; '));
|
|
99
|
-
const servers = { ...suite.mcp.servers };
|
|
100
|
-
const imported = [];
|
|
101
|
-
const skipped = [];
|
|
102
|
-
for (const entry of entries) {
|
|
103
|
-
if (!SERVER_NAME_PATTERN.test(entry.name)) {
|
|
104
|
-
skipped.push({ name: entry.name, reason: 'invalid name' });
|
|
105
|
-
continue;
|
|
106
|
-
}
|
|
107
|
-
if (Object.hasOwn(servers, entry.name) && !overwrite) {
|
|
108
|
-
skipped.push({ name: entry.name, reason: 'already exists' });
|
|
109
|
-
continue;
|
|
110
|
-
}
|
|
111
|
-
try {
|
|
112
|
-
await validateUserMcp(agentsRoot, { $schema: MCP_SCHEMA_ID, mcpServers: { ...servers, [entry.name]: entry.server } });
|
|
113
|
-
}
|
|
114
|
-
catch (reason) {
|
|
115
|
-
skipped.push({ name: entry.name, reason: reason instanceof Error ? reason.message : String(reason) });
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
servers[entry.name] = entry.server;
|
|
119
|
-
imported.push(entry.name);
|
|
120
|
-
}
|
|
121
|
-
if (imported.length > 0) {
|
|
122
|
-
await writeFileAtomic(userMcpPath(agentsRoot), `${JSON.stringify({ $schema: MCP_SCHEMA_ID, mcpServers: servers }, null, 2)}\n`, { mode: 0o600, dirMode: 0o700 });
|
|
123
|
-
}
|
|
124
|
-
return { imported, skipped };
|
|
125
|
-
}
|
|
@@ -11,9 +11,8 @@
|
|
|
11
11
|
* resolved by the Host credentials service (or launch-environment fallback)
|
|
12
12
|
* so keys never persist in plain text.
|
|
13
13
|
*/
|
|
14
|
-
import { readFile } from 'node:fs/promises';
|
|
15
14
|
import { join } from 'node:path';
|
|
16
|
-
import {
|
|
15
|
+
import { readJsonFile, writeJsonDocument } from '../json-file.js';
|
|
17
16
|
import { isSensitiveKey } from './mcp-redaction.js';
|
|
18
17
|
/** The largest timeout a bridge config and the host timer can hold. */
|
|
19
18
|
export const MAX_TIMEOUT_MS = 2_147_483_647;
|
|
@@ -28,7 +27,7 @@ export function suiteOverridePath(dataRoot, suiteId) {
|
|
|
28
27
|
/** Load one suite's persisted overrides; unreadable files yield none. */
|
|
29
28
|
export async function loadSuiteOverrides(dataRoot, suiteId) {
|
|
30
29
|
try {
|
|
31
|
-
const raw =
|
|
30
|
+
const raw = await readJsonFile(suiteOverridePath(dataRoot, suiteId));
|
|
32
31
|
if (typeof raw !== 'object' || raw === null)
|
|
33
32
|
return {};
|
|
34
33
|
const parsed = sanitizeOverrides(raw);
|
|
@@ -40,8 +39,7 @@ export async function loadSuiteOverrides(dataRoot, suiteId) {
|
|
|
40
39
|
}
|
|
41
40
|
/** Persist one suite's overrides through the harness atomic write. */
|
|
42
41
|
export async function saveSuiteOverrides(dataRoot, suiteId, overrides) {
|
|
43
|
-
|
|
44
|
-
await writeFileAtomic(path, `${JSON.stringify(overrides, null, 2)}\n`, { mode: 0o600, dirMode: 0o700 });
|
|
42
|
+
await writeJsonDocument(suiteOverridePath(dataRoot, suiteId), overrides);
|
|
45
43
|
}
|
|
46
44
|
/** Keep only recognized fields with correct shapes; drop everything else. */
|
|
47
45
|
export function sanitizeOverrides(raw) {
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { credentialRefsInServer, effectiveMcpServers, deriveServerName } from './mcp-config.js';
|
|
2
|
-
import { inspectToolRegistry } from './tool-registry-observer.js';
|
|
3
2
|
import { redactMcpConfig, redactUrl } from './mcp-redaction.js';
|
|
4
|
-
import { qualifiedSuiteId } from '
|
|
5
|
-
export { inspectToolRegistry };
|
|
3
|
+
import { qualifiedSuiteId } from '../../catalog/paths.js';
|
|
6
4
|
/** Build status rows from discovered plugin MCP definitions and observed tool names. */
|
|
7
5
|
export function buildMcpStatus(suites, diagnostics, observed, overrides = new Map()) {
|
|
8
6
|
const entries = [];
|
|
@@ -55,9 +53,15 @@ export function buildMcpStatus(suites, diagnostics, observed, overrides = new Ma
|
|
|
55
53
|
: tools.length > 0
|
|
56
54
|
? 'connected'
|
|
57
55
|
: 'degraded';
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
// A diagnostic's reason keeps the code the mount pipeline gave it. The
|
|
57
|
+
// notes this builder adds describe the declaration's own state — an
|
|
58
|
+
// override switched it off, an override rewrote it, or its surface is
|
|
59
|
+
// gone while its tools remain — so each carries the matching state code
|
|
60
|
+
// for the panel to localize.
|
|
61
|
+
const orphanNote = orphaned ? 'MCP tools remain after this plugin surface was disabled' : undefined;
|
|
62
|
+
const overrideNote = override === undefined || (state !== 'disabled' && tools.length > 0) ? undefined : disabled ? 'disabled by override' : 'modified by override';
|
|
63
|
+
const reason = orphanNote ?? diagnostic?.reason ?? overrideNote;
|
|
64
|
+
const code = orphanNote !== undefined ? 'orphaned-tools' : (diagnostic?.code ?? (overrideNote === undefined ? undefined : disabled ? 'disabled-override' : 'modified-override'));
|
|
61
65
|
entries.push({
|
|
62
66
|
id: `plugin:${suiteKey}/${serverKey}`,
|
|
63
67
|
name: serverName,
|
|
@@ -76,8 +80,9 @@ export function buildMcpStatus(suites, diagnostics, observed, overrides = new Ma
|
|
|
76
80
|
...(policy.enabledTools === undefined ? {} : { suiteEnabledTools: policy.enabledTools }),
|
|
77
81
|
...(policy.suiteDisabledTools === undefined ? {} : { suiteDisabledTools: policy.suiteDisabledTools }),
|
|
78
82
|
...(override?.disabledTools === undefined ? {} : { userDisabledTools: override.disabledTools }),
|
|
79
|
-
...(
|
|
83
|
+
...(code === undefined ? {} : { code }),
|
|
80
84
|
...(reason === undefined ? {} : { reason }),
|
|
85
|
+
...(diagnostic?.causes === undefined ? {} : { causes: diagnostic.causes }),
|
|
81
86
|
...(credentialRefs.length === 0 ? {} : { credentialRefs })
|
|
82
87
|
});
|
|
83
88
|
}
|
|
@@ -102,6 +107,7 @@ export function buildMcpStatus(suites, diagnostics, observed, overrides = new Ma
|
|
|
102
107
|
config: redactMcpConfig(stale.server),
|
|
103
108
|
tools: observedTools(tools),
|
|
104
109
|
reason: 'MCP tools remain after this plugin was disabled or uninstalled',
|
|
110
|
+
code: 'orphaned-tools',
|
|
105
111
|
...(staleRefs.length === 0 ? {} : { credentialRefs: staleRefs })
|
|
106
112
|
});
|
|
107
113
|
continue;
|
|
@@ -9,16 +9,15 @@
|
|
|
9
9
|
import { qualifiedSuiteId } from '../catalog/paths.js';
|
|
10
10
|
import { isDirectory } from '../catalog/fs-probes.js';
|
|
11
11
|
import { discoverSuitesInSource } from '../catalog/suite-scanner.js';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import { applyLspOverrides, lspConfig, restoreRedactedConfig, saveLspOverride, validateServerLsp, validateServerMcp } from '../runtime/server-config.js';
|
|
12
|
+
import { probeHostMcpClient } from './mcp/mcp-backend.js';
|
|
13
|
+
import { declaredMcpPolicy, namespaceMcpPolicy, resolveMcpPolicy } from './mcp/mcp-config.js';
|
|
14
|
+
import { loadLspServers, saveLspServers } from './lsp/lsp-direct-config.js';
|
|
15
|
+
import { addUserMcpServer, loadUserMcpSuite, USER_MCP_SOURCE, USER_MCP_SUITE } from './mcp/mcp-direct-config.js';
|
|
16
|
+
import { applyOverride, loadSuiteOverrides, mergeOverridePatch, parseMcpPolicyPatch, saveSuiteOverrides, withoutPolicyFields } from './mcp/mcp-overrides.js';
|
|
17
|
+
import { redactMcpConfig, redactMcpOverrides } from './mcp/mcp-redaction.js';
|
|
18
|
+
import { buildMcpStatus } from './mcp/mcp-status.js';
|
|
19
|
+
import { resolveRegion } from './regions.js';
|
|
20
|
+
import { applyLspOverrides, lspConfig, restoreRedactedConfig, saveLspOverride, validateServerLsp, validateServerMcp } from './server-config.js';
|
|
22
21
|
/** The host compatibility client mounts servers without a startup timeout or tool filters. */
|
|
23
22
|
const HOST_STARTUP_TIMEOUT_UNSUPPORTED = 'the host MCP backend cannot enforce a startup timeout; switch the MCP backend to the built-in client to set one';
|
|
24
23
|
const HOST_TOOL_FILTER_UNSUPPORTED = 'the host MCP backend cannot enforce tool filters; switch the MCP backend to the built-in client to choose tools';
|
|
@@ -64,29 +63,6 @@ export class McpService {
|
|
|
64
63
|
await this.context.notifyChanged(true);
|
|
65
64
|
});
|
|
66
65
|
}
|
|
67
|
-
/**
|
|
68
|
-
* Import several pasted services in one write. Each entry is checked on its
|
|
69
|
-
* own and reports back with its own outcome, so a paste of many services
|
|
70
|
-
* never fails as a block.
|
|
71
|
-
*/
|
|
72
|
-
async importServers(servers, overwrite) {
|
|
73
|
-
if (!Array.isArray(servers))
|
|
74
|
-
throw new Error('servers must be an array');
|
|
75
|
-
const entries = servers.map(entry => {
|
|
76
|
-
if (typeof entry !== 'object' || entry === null)
|
|
77
|
-
throw new Error('each server must be an object');
|
|
78
|
-
const { name, config } = entry;
|
|
79
|
-
if (typeof name !== 'string' || name === '')
|
|
80
|
-
throw new Error('each server needs a name');
|
|
81
|
-
return { name, server: config };
|
|
82
|
-
});
|
|
83
|
-
return this.context.enqueue(async () => {
|
|
84
|
-
const result = await importUserMcpServers(this.context.agentsRoot, entries, overwrite);
|
|
85
|
-
if (result.imported.length > 0)
|
|
86
|
-
await this.context.notifyChanged(true);
|
|
87
|
-
return result;
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
66
|
/** Read effective service configuration without exposing credential literals. */
|
|
91
67
|
async serverConfig(kind, id) {
|
|
92
68
|
const config = await this.resolveServerConfig(kind, id);
|
|
@@ -272,8 +248,8 @@ export class McpService {
|
|
|
272
248
|
* everything the plugin-config card renders.
|
|
273
249
|
*/
|
|
274
250
|
async backendInfo() {
|
|
275
|
-
const [backend, hostClient, regionSetting
|
|
276
|
-
return { backend, hostClient, downloadRegion: { setting: regionSetting, effective: resolveRegion(regionSetting,
|
|
251
|
+
const [backend, hostClient, regionSetting] = await Promise.all([this.ports.mcpBackend(), probeHostMcpClient(), this.ports.downloadRegion()]);
|
|
252
|
+
return { backend, hostClient, downloadRegion: { setting: regionSetting, effective: resolveRegion(regionSetting, this.ports.localePreference()) } };
|
|
277
253
|
}
|
|
278
254
|
/**
|
|
279
255
|
* Switch the MCP mount backend and remount every suite server through it.
|