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.
Files changed (158) hide show
  1. package/README.md +2 -2
  2. package/README.zh.md +2 -2
  3. package/client/client.js +1594 -1732
  4. package/client/client.js.map +1 -1
  5. package/lib/application/agent-roles.js +53 -0
  6. package/lib/application/catalog-context.js +2 -2
  7. package/lib/application/catalog.js +4 -8
  8. package/lib/{runtime → application}/deadline.js +1 -1
  9. package/lib/application/details.js +3 -3
  10. package/lib/application/json-file.js +43 -0
  11. package/lib/{runtime → application/lsp}/lsp-direct-config.js +9 -7
  12. package/lib/application/lsp/lsp-server-state.js +17 -0
  13. package/lib/{runtime → application/lsp}/lsp-status.js +22 -7
  14. package/lib/{runtime → application/lsp}/profile-seam.js +1 -1
  15. package/lib/application/lsp-service.js +5 -5
  16. package/lib/{runtime → application/mcp}/mcp-backend.js +6 -21
  17. package/lib/{runtime → application/mcp}/mcp-config.js +4 -4
  18. package/lib/{runtime → application/mcp}/mcp-direct-config.js +8 -49
  19. package/lib/{runtime → application/mcp}/mcp-overrides.js +3 -5
  20. package/lib/{runtime → application/mcp}/mcp-status.js +13 -7
  21. package/lib/application/mcp-service.js +11 -35
  22. package/lib/application/panel-resources.js +48 -11
  23. package/lib/{runtime → application/panels}/user-hooks.js +2 -2
  24. package/lib/{runtime → application/panels}/user-store.js +2 -2
  25. package/lib/application/ports.js +1 -0
  26. package/lib/application/project-agent-roles.js +1 -1
  27. package/lib/{runtime → application}/server-config.js +7 -17
  28. package/lib/application/snapshot-cache.js +1 -1
  29. package/lib/application/source-store.js +3 -4
  30. package/lib/{runtime → application/state}/state-store.js +5 -7
  31. package/lib/{runtime → application/state}/storage-migration.js +4 -4
  32. package/lib/catalog/source-catalog.js +0 -4
  33. package/lib/contracts/lsp.js +1 -0
  34. package/lib/contracts/market.js +0 -1
  35. package/lib/contracts/mcp.js +1 -0
  36. package/lib/index.js +64 -34
  37. package/lib/routes.js +2 -9
  38. package/lib/runtime/{agent-role-router.js → agents/agent-role-router.js} +7 -53
  39. package/lib/runtime/{subagent-catalog.js → agents/subagent-catalog.js} +24 -16
  40. package/lib/runtime/core/context.js +31 -0
  41. package/lib/runtime/{reconciler.js → core/reconciler.js} +5 -5
  42. package/lib/runtime/host/failure-detail.js +58 -0
  43. package/lib/runtime/{feedback-tool.js → host/feedback-tool.js} +1 -1
  44. package/lib/runtime/host/host-locale.js +83 -0
  45. package/lib/runtime/{settings-namespace.js → host/settings-namespace.js} +44 -66
  46. package/lib/runtime/host/shell-path.js +196 -0
  47. package/lib/runtime/{tool-registry-observer.js → host/tool-registry-observer.js} +2 -1
  48. package/lib/runtime/{lsp-mounts.js → lsp/lsp-mounts.js} +69 -20
  49. package/lib/runtime/{mcp-client → mcp/bridge}/bridge.js +52 -22
  50. package/lib/runtime/{mcp-client → mcp/bridge}/connection.js +2 -2
  51. package/lib/runtime/{mcp-client → mcp/bridge}/oauth.js +1 -1
  52. package/lib/runtime/{mcp-client → mcp/bridge}/tools.js +2 -2
  53. package/lib/runtime/{mcp-auth-record.js → mcp/mcp-auth-record.js} +1 -1
  54. package/lib/runtime/{mcp-mounts.js → mcp/mcp-mounts.js} +14 -7
  55. package/lib/runtime/{user-commands.js → panels/user-commands.js} +3 -3
  56. package/lib/runtime/{user-panels.js → panels/user-panels.js} +2 -2
  57. package/lib/runtime/{commands-mounts.js → surfaces/commands-mounts.js} +7 -7
  58. package/lib/runtime/{hooks-mounts.js → surfaces/hooks-mounts.js} +2 -2
  59. package/lib/runtime/{project-runtime.js → surfaces/project-runtime.js} +5 -5
  60. package/lib/runtime/{skills-provider.js → surfaces/skills-provider.js} +4 -4
  61. package/lib/types/application/agent-roles.d.ts +30 -0
  62. package/lib/types/application/catalog.d.ts +4 -7
  63. package/lib/types/application/details.d.ts +1 -1
  64. package/lib/types/application/json-file.d.ts +12 -0
  65. package/lib/types/{runtime → application/lsp}/lsp-direct-config.d.ts +1 -1
  66. package/lib/types/{runtime → application/lsp}/lsp-status.d.ts +3 -3
  67. package/lib/types/{runtime → application/mcp}/mcp-backend.d.ts +24 -22
  68. package/lib/types/{runtime/mcp-client/config.d.ts → application/mcp/mcp-bridge-config.d.ts} +11 -1
  69. package/lib/types/{runtime → application/mcp}/mcp-config.d.ts +6 -7
  70. package/lib/types/application/mcp/mcp-direct-config.d.ts +17 -0
  71. package/lib/types/{runtime → application/mcp}/mcp-overrides.d.ts +1 -1
  72. package/lib/types/{runtime → application/mcp}/mcp-status.d.ts +7 -7
  73. package/lib/types/application/mcp-service.d.ts +4 -11
  74. package/lib/types/application/panel-resources.d.ts +9 -2
  75. package/lib/types/{runtime → application/panels}/user-hooks.d.ts +1 -1
  76. package/lib/types/{runtime → application/panels}/user-store.d.ts +1 -1
  77. package/lib/types/application/ports.d.ts +17 -3
  78. package/lib/types/application/project-agent-roles.d.ts +1 -1
  79. package/lib/types/application/queries.d.ts +3 -6
  80. package/lib/types/{runtime → application}/regions.d.ts +1 -1
  81. package/lib/types/{runtime → application/state}/state-store.d.ts +2 -2
  82. package/lib/types/catalog/source-catalog.d.ts +0 -2
  83. package/lib/types/contracts/lsp-status.d.ts +6 -0
  84. package/lib/types/contracts/lsp.d.ts +16 -0
  85. package/lib/types/contracts/market.d.ts +1 -4
  86. package/lib/types/contracts/mcp-status.d.ts +21 -2
  87. package/lib/types/contracts/mcp.d.ts +20 -0
  88. package/lib/types/index.d.ts +87 -1
  89. package/lib/types/model/types.d.ts +0 -14
  90. package/lib/types/runtime/{agent-role-router.d.ts → agents/agent-role-router.d.ts} +3 -28
  91. package/lib/types/runtime/{subagent-catalog.d.ts → agents/subagent-catalog.d.ts} +6 -6
  92. package/lib/types/runtime/core/context.d.ts +14 -0
  93. package/lib/types/runtime/{reconcile-scheduler.d.ts → core/reconcile-scheduler.d.ts} +2 -2
  94. package/lib/types/runtime/{reconciler.d.ts → core/reconciler.d.ts} +8 -8
  95. package/lib/types/runtime/host/failure-detail.d.ts +6 -0
  96. package/lib/types/runtime/host/host-locale.d.ts +66 -0
  97. package/lib/types/runtime/{model-catalog.d.ts → host/model-catalog.d.ts} +1 -1
  98. package/lib/types/runtime/{settings-namespace.d.ts → host/settings-namespace.d.ts} +42 -32
  99. package/lib/types/runtime/host/shell-path.d.ts +70 -0
  100. package/lib/types/runtime/{lsp-mounts.d.ts → lsp/lsp-mounts.d.ts} +17 -11
  101. package/lib/types/runtime/{mcp-client → mcp/bridge}/bridge.d.ts +1 -1
  102. package/lib/types/runtime/{mcp-client → mcp/bridge}/connection.d.ts +3 -11
  103. package/lib/types/runtime/{mcp-client → mcp/bridge}/host-contract.d.ts +1 -1
  104. package/lib/types/runtime/{mcp-client → mcp/bridge}/oauth.d.ts +1 -1
  105. package/lib/types/runtime/{mcp-client → mcp/bridge}/transport.d.ts +1 -1
  106. package/lib/types/runtime/{mcp-auth-record.d.ts → mcp/mcp-auth-record.d.ts} +1 -1
  107. package/lib/types/runtime/{mcp-credentials.d.ts → mcp/mcp-credentials.d.ts} +1 -1
  108. package/lib/types/runtime/{mcp-mounts.d.ts → mcp/mcp-mounts.d.ts} +5 -11
  109. package/lib/types/runtime/{user-commands.d.ts → panels/user-commands.d.ts} +1 -1
  110. package/lib/types/runtime/{user-panels.d.ts → panels/user-panels.d.ts} +1 -1
  111. package/lib/types/runtime/{commands-mounts.d.ts → surfaces/commands-mounts.d.ts} +2 -2
  112. package/lib/types/runtime/{hooks-mounts.d.ts → surfaces/hooks-mounts.d.ts} +1 -1
  113. package/lib/types/runtime/{project-runtime.d.ts → surfaces/project-runtime.d.ts} +3 -3
  114. package/lib/types/runtime/{skills-provider.d.ts → surfaces/skills-provider.d.ts} +1 -1
  115. package/package.json +39 -33
  116. package/lib/runtime/host-locale.js +0 -89
  117. package/lib/runtime/legacy-root-migration.js +0 -12
  118. package/lib/runtime/lsp-server-state.js +0 -19
  119. package/lib/runtime/mcp-client/json-schema-subset.js +0 -181
  120. package/lib/types/runtime/host-locale.d.ts +0 -31
  121. package/lib/types/runtime/legacy-root-migration.d.ts +0 -2
  122. package/lib/types/runtime/mcp-client/json-schema-subset.d.ts +0 -58
  123. package/lib/types/runtime/mcp-direct-config.d.ts +0 -39
  124. /package/lib/{runtime/mcp-client/config.js → application/mcp/mcp-bridge-config.js} +0 -0
  125. /package/lib/{runtime → application/mcp}/mcp-redaction.js +0 -0
  126. /package/lib/{runtime → application}/regions.js +0 -0
  127. /package/lib/runtime/{agent-role-names.js → agents/agent-role-names.js} +0 -0
  128. /package/lib/runtime/{mount-lifecycle.js → core/mount-lifecycle.js} +0 -0
  129. /package/lib/runtime/{reconcile-scheduler.js → core/reconcile-scheduler.js} +0 -0
  130. /package/lib/runtime/{source-auto-update.js → core/source-auto-update.js} +0 -0
  131. /package/lib/runtime/{timer-seat.js → core/timer-seat.js} +0 -0
  132. /package/lib/runtime/{model-catalog.js → host/model-catalog.js} +0 -0
  133. /package/lib/runtime/{plugin-message-source.js → host/plugin-message-source.js} +0 -0
  134. /package/lib/runtime/{mcp-client → mcp/bridge}/host-contract.js +0 -0
  135. /package/lib/runtime/{mcp-client → mcp/bridge}/host-seams.js +0 -0
  136. /package/lib/runtime/{mcp-client → mcp/bridge}/plugin-identity.js +0 -0
  137. /package/lib/runtime/{mcp-client → mcp/bridge}/projection.js +0 -0
  138. /package/lib/runtime/{mcp-client → mcp/bridge}/transport.js +0 -0
  139. /package/lib/runtime/{mcp-credentials.js → mcp/mcp-credentials.js} +0 -0
  140. /package/lib/runtime/{dynamic-context.js → surfaces/dynamic-context.js} +0 -0
  141. /package/lib/types/{runtime → application}/deadline.d.ts +0 -0
  142. /package/lib/types/{runtime → application/lsp}/lsp-server-state.d.ts +0 -0
  143. /package/lib/types/{runtime → application/lsp}/profile-seam.d.ts +0 -0
  144. /package/lib/types/{runtime → application/mcp}/mcp-redaction.d.ts +0 -0
  145. /package/lib/types/{runtime → application}/server-config.d.ts +0 -0
  146. /package/lib/types/{runtime → application/state}/storage-migration.d.ts +0 -0
  147. /package/lib/types/runtime/{agent-role-names.d.ts → agents/agent-role-names.d.ts} +0 -0
  148. /package/lib/types/runtime/{mount-lifecycle.d.ts → core/mount-lifecycle.d.ts} +0 -0
  149. /package/lib/types/runtime/{source-auto-update.d.ts → core/source-auto-update.d.ts} +0 -0
  150. /package/lib/types/runtime/{timer-seat.d.ts → core/timer-seat.d.ts} +0 -0
  151. /package/lib/types/runtime/{feedback-tool.d.ts → host/feedback-tool.d.ts} +0 -0
  152. /package/lib/types/runtime/{plugin-message-source.d.ts → host/plugin-message-source.d.ts} +0 -0
  153. /package/lib/types/runtime/{tool-registry-observer.d.ts → host/tool-registry-observer.d.ts} +0 -0
  154. /package/lib/types/runtime/{mcp-client → mcp/bridge}/host-seams.d.ts +0 -0
  155. /package/lib/types/runtime/{mcp-client → mcp/bridge}/plugin-identity.d.ts +0 -0
  156. /package/lib/types/runtime/{mcp-client → mcp/bridge}/projection.d.ts +0 -0
  157. /package/lib/types/runtime/{mcp-client → mcp/bridge}/tools.d.ts +0 -0
  158. /package/lib/types/runtime/{dynamic-context.d.ts → surfaces/dynamic-context.d.ts} +0 -0
@@ -1,9 +1,11 @@
1
- import { DIRECT_LSP_SUITE_ID } from './lsp-status.js';
2
- import { SerialPassQueue, RetryScheduler } from './mount-lifecycle.js';
3
- import { describeLegacySeam, findLegacyLspSeams } from './profile-seam.js';
4
- import { qualifiedSuiteId, suiteDataDir } from '../catalog/paths.js';
5
- import { expandPluginPaths, pluginRootOf } from '../catalog/plugin-variables.js';
6
- import { effectiveSurfaces } from '../model/types.js';
1
+ import { DIRECT_LSP_SUITE_ID } from '../../application/lsp/lsp-status.js';
2
+ import { SerialPassQueue, RetryScheduler } from '../core/mount-lifecycle.js';
3
+ import { describeLegacySeam, findLegacyLspSeams } from '../../application/lsp/profile-seam.js';
4
+ import { qualifiedSuiteId, suiteDataDir } from '../../catalog/paths.js';
5
+ import { expandPluginPaths, pluginRootOf } from '../../catalog/plugin-variables.js';
6
+ import { causeMessages } from '../host/failure-detail.js';
7
+ import { resolveDeclaredCommand } from '../host/shell-path.js';
8
+ import { effectiveSurfaces } from '../../model/types.js';
7
9
  /** Build one dsh-lsp-stdio Config.servers entry from a normalized spec. */
8
10
  export function toLspServerConfig(spec) {
9
11
  return {
@@ -110,16 +112,21 @@ export class LspMountRegistry {
110
112
  // otherwise shadow each other's mount and diagnostics.
111
113
  const key = qualifiedSuiteId(suite.sourceId, suite.id);
112
114
  const config = {};
115
+ const facts = [];
116
+ const pathExtensions = [];
113
117
  const root = pluginRootOf(suite);
114
118
  const context = {
115
119
  ...(root === undefined ? {} : { root }),
116
120
  ...(root === undefined || this.pluginDataRoot === undefined ? {} : { data: suiteDataDir(this.pluginDataRoot, suite.sourceId, suite.id) })
117
121
  };
118
- for (const spec of servers)
119
- if (!disabled.has(`${key}/${spec.key}`))
120
- config[`${key}/${spec.key}`] = expandLspServerConfig(toLspServerConfig(spec), context);
122
+ for (const spec of servers) {
123
+ if (disabled.has(`${key}/${spec.key}`))
124
+ continue;
125
+ const serverConfig = expandLspServerConfig(toLspServerConfig(spec), context);
126
+ config[`${key}/${spec.key}`] = await this.resolveServerCommand(serverConfig, spec.key, facts, pathExtensions);
127
+ }
121
128
  if (Object.keys(config).length > 0)
122
- wanted.set(key, { suite, config });
129
+ wanted.set(key, { suite, config, facts, pathExtensions });
123
130
  }
124
131
  // Direct user-configured servers ride the same mount path under the
125
132
  // sentinel suite id, so their lifecycle (retries, diagnostics, disposal)
@@ -128,9 +135,13 @@ export class LspMountRegistry {
128
135
  const directKeys = Object.keys(direct);
129
136
  if (directKeys.length > 0) {
130
137
  const config = {};
131
- for (const [key, spec] of Object.entries(direct))
132
- if (!disabled.has(`${DIRECT_LSP_SUITE_ID}/${key}`))
133
- config[`${DIRECT_LSP_SUITE_ID}/${key}`] = toLspServerConfig(spec);
138
+ const facts = [];
139
+ const pathExtensions = [];
140
+ for (const [key, spec] of Object.entries(direct)) {
141
+ if (disabled.has(`${DIRECT_LSP_SUITE_ID}/${key}`))
142
+ continue;
143
+ config[`${DIRECT_LSP_SUITE_ID}/${key}`] = await this.resolveServerCommand(toLspServerConfig(spec), key, facts, pathExtensions);
144
+ }
134
145
  if (Object.keys(config).length > 0)
135
146
  wanted.set(DIRECT_LSP_SUITE_ID, {
136
147
  suite: {
@@ -147,7 +158,9 @@ export class LspMountRegistry {
147
158
  activeSurfaces: effectiveSurfaces(undefined),
148
159
  errors: []
149
160
  },
150
- config
161
+ config,
162
+ facts,
163
+ pathExtensions
151
164
  });
152
165
  }
153
166
  // The last remaining server releases the capability seam; the first one mounts it.
@@ -168,9 +181,16 @@ export class LspMountRegistry {
168
181
  this.lastDiagnostics.delete(key);
169
182
  continue;
170
183
  }
184
+ const causes = [...(capabilityFailure?.causes ?? []), ...entry.facts];
171
185
  const failure = capabilityFailure === undefined
172
- ? await this.mountWith(key, entry.suite, entry.config)
173
- : { suiteId: key, serverKey: Object.keys(entry.config).join(','), reason: capabilityFailure.reason, code: capabilityFailure.code };
186
+ ? await this.mountWith(key, entry.suite, entry.config, entry.facts, entry.pathExtensions)
187
+ : {
188
+ suiteId: key,
189
+ serverKey: Object.keys(entry.config).join(','),
190
+ reason: capabilityFailure.reason,
191
+ code: capabilityFailure.code,
192
+ ...(causes.length === 0 ? {} : { causes })
193
+ };
174
194
  if (failure !== undefined) {
175
195
  diagnostics.push(failure);
176
196
  this.lastDiagnostics.set(key, failure);
@@ -182,6 +202,30 @@ export class LspMountRegistry {
182
202
  }
183
203
  return diagnostics;
184
204
  }
205
+ /**
206
+ * Resolve one server's declared command, extending its `PATH` from the
207
+ * login shell only when the current environment cannot find the command.
208
+ *
209
+ * An explicit `env.PATH` is a declaration and is left verbatim; the command
210
+ * itself is never rewritten. A resolution fact (the extension, or the
211
+ * searched `PATH` when it still failed) is appended to the suite's facts so
212
+ * it reaches the mount diagnostic; a fact is also a PATH extension exactly
213
+ * when `resolution.path` is set, and rides `pathExtensions` for the trace log.
214
+ */
215
+ async resolveServerCommand(config, serverKey, facts, pathExtensions) {
216
+ const resolution = await resolveDeclaredCommand(this.ctx, config.command, config.env ?? {});
217
+ if (resolution === undefined)
218
+ return config;
219
+ if (resolution.diagnostic !== undefined) {
220
+ const fact = `${serverKey}: ${resolution.diagnostic}`;
221
+ facts.push(fact);
222
+ if (resolution.path !== undefined)
223
+ pathExtensions.push(fact);
224
+ }
225
+ if (resolution.path === undefined)
226
+ return config;
227
+ return { ...config, env: { ...(config.env ?? {}), PATH: resolution.path } };
228
+ }
185
229
  /**
186
230
  * Mount the capability seam this plugin delivers end to end: `ctx.lsp`, then the model-facing
187
231
  * `lsp` tool.
@@ -238,9 +282,9 @@ export class LspMountRegistry {
238
282
  // `service "lsp" has been registered` / `tool "lsp" is already registered`: another layer
239
283
  // owns the seam. Report the layer to drop rather than falling back to its version.
240
284
  if (/already registered|has been registered/.test(message)) {
241
- return { reason: await this.seamConflictReason(specifier, message), code: 'seam-conflict' };
285
+ return { reason: await this.seamConflictReason(specifier, message), code: 'seam-conflict', causes: causeMessages(error) };
242
286
  }
243
- return { reason: `mount failed: ${message}`, code: 'mount-failed' };
287
+ return { reason: `mount failed: ${message}`, code: 'mount-failed', causes: causeMessages(error) };
244
288
  }
245
289
  this.capability.push(handle);
246
290
  return undefined;
@@ -297,7 +341,7 @@ export class LspMountRegistry {
297
341
  return this.live.size > 0;
298
342
  }
299
343
  /** Mount one suite's full server table as a single `dsh-lsp-stdio` instance. */
300
- async mountWith(key, suite, servers) {
344
+ async mountWith(key, suite, servers, facts = [], pathExtensions = []) {
301
345
  const hostKeys = Object.keys(servers).join(',');
302
346
  const module = await this.loadHost();
303
347
  if (module === undefined) {
@@ -330,9 +374,14 @@ export class LspMountRegistry {
330
374
  suiteId: key,
331
375
  serverKey: hostKeys,
332
376
  reason: `mount failed: ${message}`,
333
- code: conflict ? 'seam-conflict' : 'mount-failed'
377
+ code: conflict ? 'seam-conflict' : 'mount-failed',
378
+ causes: [...causeMessages(error), ...facts]
334
379
  };
335
380
  }
381
+ // The mount is live: only a genuine PATH extension is worth a trace, and
382
+ // resolveServerCommand already marked those facts structurally.
383
+ for (const fact of pathExtensions)
384
+ this.ctx.logger?.info?.(`[dsh-agent-plugins-market] ${key}: ${fact}`);
336
385
  this.live.set(key, { fingerprint: JSON.stringify(servers), suiteId: key, serverKeys: Object.keys(servers), disposer: () => handle.dispose() });
337
386
  return undefined;
338
387
  }
@@ -20,7 +20,9 @@
20
20
  * @module runtime/mcp-client/bridge
21
21
  */
22
22
  import { resolveReconnectPolicy, startConnection } from './connection.js';
23
- import { validateConfig } from './config.js';
23
+ import { validateConfig } from '../../../application/mcp/mcp-bridge-config.js';
24
+ import { resolveDeclaredCommand } from '../../host/shell-path.js';
25
+ import { optionalService } from '../../core/context.js';
24
26
  /** Cordis plugin name used by loader diagnostics. */
25
27
  export const name = 'market-mcp-client';
26
28
  /** Services required by this plugin. */
@@ -33,19 +35,6 @@ export const inject = ['tools'];
33
35
  * defense for direct programmatic loads.
34
36
  */
35
37
  const activeServerNames = new WeakMap();
36
- /** Tolerantly read an optional service from the cordis context. */
37
- function optionalService(ctx, serviceName) {
38
- try {
39
- const reader = ctx;
40
- const service = reader.get?.(serviceName);
41
- // A missing service either reads as undefined or throws (cordis strict
42
- // mode); both mean "not mounted".
43
- return service === null ? undefined : service;
44
- }
45
- catch {
46
- return undefined;
47
- }
48
- }
49
38
  /** Adapt the cordis context onto the structural host the bridge modules use. */
50
39
  function toToolHost(ctx) {
51
40
  const logger = {
@@ -74,6 +63,32 @@ function toToolHost(ctx) {
74
63
  getService: serviceName => optionalService(ctx, serviceName)
75
64
  };
76
65
  }
66
+ /**
67
+ * Resolve one stdio command before the SDK spawns it, extending the child
68
+ * `PATH` from the login shell only when the current environment cannot find
69
+ * the command.
70
+ *
71
+ * An explicit `env.PATH` is the author's or user's declaration and is never
72
+ * touched: the bridge respects it verbatim and does not even probe. Without
73
+ * one, the child inherits the scrubbed parent `PATH`; when that cannot resolve
74
+ * the command (the desktop case, where `npx` is absent) the login shell's
75
+ * directories are appended, and the extension travels beside the config as
76
+ * `config.env.PATH`.
77
+ *
78
+ * Resolution never rejects: a command that stays unresolved keeps today's
79
+ * behavior (a spawn that fails with its own `ENOENT`), and the searched `PATH`
80
+ * is returned as a diagnostic so the failure can say what was tried.
81
+ */
82
+ async function resolveStdioCommand(ctx, config) {
83
+ if (config.transport !== 'stdio')
84
+ return { config };
85
+ const resolution = await resolveDeclaredCommand(ctx, config.command, config.env);
86
+ if (resolution === undefined)
87
+ return { config };
88
+ const { path, diagnostic } = resolution;
89
+ const resolved = path === undefined ? config : { ...config, env: { ...config.env, PATH: path } };
90
+ return { config: resolved, ...(diagnostic === undefined ? {} : { diagnostic }) };
91
+ }
77
92
  /**
78
93
  * Connect one MCP server and publish its initial tool generation before
79
94
  * activation. Remains explicitly `async`: Cordis treats a prototype-bearing
@@ -87,7 +102,9 @@ export async function apply(ctx, config) {
87
102
  // Fail loud at load: programmatic construction bypasses any schema layer,
88
103
  // so every invariant is re-judged here before any effect registers.
89
104
  validateConfig(config);
90
- const reconnect = resolveReconnectPolicy(config.reconnect, `mcp-client(${config.serverName}): reconnect`);
105
+ const resolution = await resolveStdioCommand(ctx, config);
106
+ const resolvedConfig = resolution.config;
107
+ const reconnect = resolveReconnectPolicy(resolvedConfig.reconnect, `mcp-client(${resolvedConfig.serverName}): reconnect`);
91
108
  // Reserve the namespace next: a duplicate `serverName` fails THIS instance
92
109
  // at load with an actionable error and leaves the earlier instance intact.
93
110
  ctx.effect(() => {
@@ -96,11 +113,11 @@ export async function apply(ctx, config) {
96
113
  names = new Set();
97
114
  activeServerNames.set(ctx.root, names);
98
115
  }
99
- if (names.has(config.serverName)) {
100
- throw new Error(`market-mcp-client: serverName "${config.serverName}" is already in use by another bridge instance — pick a unique serverName`);
116
+ if (names.has(resolvedConfig.serverName)) {
117
+ throw new Error(`market-mcp-client: serverName "${resolvedConfig.serverName}" is already in use by another bridge instance — pick a unique serverName`);
101
118
  }
102
- names.add(config.serverName);
103
- return () => void names.delete(config.serverName);
119
+ names.add(resolvedConfig.serverName);
120
+ return () => void names.delete(resolvedConfig.serverName);
104
121
  }, 'market-mcp-client.serverName');
105
122
  // The supervisor owns the client/transport generations, the reconnect
106
123
  // loop, and the live tool registrations; disposal stops reconnection,
@@ -110,7 +127,7 @@ export async function apply(ctx, config) {
110
127
  // no-persistence configuration.
111
128
  const host = toToolHost(ctx);
112
129
  const credentials = optionalService(ctx, 'credentials');
113
- const connection = startConnection(host, config, reconnect, credentials);
130
+ const connection = startConnection(host, resolvedConfig, reconnect, credentials);
114
131
  ctx.effect(() => {
115
132
  return () => connection.dispose();
116
133
  }, 'market-mcp-client.connection');
@@ -120,7 +137,20 @@ export async function apply(ctx, config) {
120
137
  // fiber (Cordis rolls it back); otherwise the error is logged and the
121
138
  // supervisor enters its reconnect loop.
122
139
  const outcome = await connection.ready;
123
- if (outcome.error !== undefined && config.failOnStartupError) {
124
- throw new Error(`mcp-client(${config.serverName}): initial connection or tool synchronization failed`, { cause: outcome.error });
140
+ if (outcome.error !== undefined && resolvedConfig.failOnStartupError) {
141
+ // The resolution fact explains why the spawn failed (or what was searched),
142
+ // so it rides the cause chain into the mount diagnostic ahead of the raw
143
+ // spawn error. On the happy path there is nothing to add and the chain is
144
+ // exactly today's.
145
+ const cause = resolution.diagnostic === undefined ? outcome.error : new Error(resolution.diagnostic, { cause: outcome.error });
146
+ throw new Error(`mcp-client(${resolvedConfig.serverName}): initial connection or tool synchronization failed`, { cause });
147
+ }
148
+ // A command that resolved only after the login shell extended PATH leaves a
149
+ // trace, so "why does this work now" is answerable from the log. With
150
+ // failOnStartupError=false a failed resolution reaches here too; the env.PATH
151
+ // guard excludes it, because PATH is only set on a successful extension —
152
+ // the transport check is the narrowing that makes env exist at all.
153
+ if (resolvedConfig.transport === 'stdio' && resolvedConfig.env.PATH !== undefined && resolution.diagnostic !== undefined) {
154
+ ctx.logger?.info?.(`market-mcp-client(${resolvedConfig.serverName}): ${resolution.diagnostic}`);
125
155
  }
126
156
  }
@@ -23,11 +23,11 @@
23
23
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
24
24
  import { ToolListChangedNotificationSchema } from '@modelcontextprotocol/sdk/types.js';
25
25
  import { MAX_TIMER_DELAY_MS } from './host-seams.js';
26
- import { redactErrorMessage } from '../mcp-redaction.js';
26
+ import { redactErrorMessage } from '../../../application/mcp/mcp-redaction.js';
27
27
  import { PLUGIN_NAME, PLUGIN_VERSION } from './plugin-identity.js';
28
28
  import { createTransport } from './transport.js';
29
29
  import { syncTools } from './tools.js';
30
- import { DEFAULT_STARTUP_TIMEOUT_MS } from './config.js';
30
+ import { DEFAULT_STARTUP_TIMEOUT_MS } from '../../../application/mcp/mcp-bridge-config.js';
31
31
  /** Defaults shared by the config and {@link resolveReconnectPolicy}. */
32
32
  export const RECONNECT_DEFAULTS = Object.freeze({
33
33
  enabled: true,
@@ -31,7 +31,7 @@ import { createServer } from 'node:http';
31
31
  import { randomBytes } from 'node:crypto';
32
32
  import { isDeepStrictEqual } from 'node:util';
33
33
  import { credentialKey, scrubbedParentEnv } from './host-seams.js';
34
- import { redactErrorMessage, redactUrl } from '../mcp-redaction.js';
34
+ import { redactErrorMessage, redactUrl } from '../../../application/mcp/mcp-redaction.js';
35
35
  import { PLUGIN_NAME, PLUGIN_VERSION } from './plugin-identity.js';
36
36
  /** OAuth client identity presented during dynamic registration. */
37
37
  const CLIENT_NAME = `DeepSeek Harness (${PLUGIN_NAME})`;
@@ -19,9 +19,9 @@ import { createHash } from 'node:crypto';
19
19
  import { isDeepStrictEqual } from 'node:util';
20
20
  import { ListToolsResultSchema } from '@modelcontextprotocol/sdk/types.js';
21
21
  import { z } from 'zod';
22
- import { assertSupportedJsonSchema } from './json-schema-subset.js';
22
+ import { assertSupportedJsonSchema } from '@deepseek-ai/dsh-tools';
23
23
  import { containsImage, extractText, prepareImageProjection } from './projection.js';
24
- import { redactErrorMessage } from '../mcp-redaction.js';
24
+ import { redactErrorMessage } from '../../../application/mcp/mcp-redaction.js';
25
25
  /**
26
26
  * DeepSeek function-name contract: at most 64 characters. Wire-protocol
27
27
  * constant, not configuration.
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * @module runtime/mcp-auth-record
10
10
  */
11
- import { credentialKey } from './mcp-client/host-seams.js';
11
+ import { credentialKey } from './bridge/host-seams.js';
12
12
  /** Credential-record scope for MCP OAuth state; per-server ids follow it. */
13
13
  export const MCP_AUTH_RECORD_SCOPE = 'mcp-auth';
14
14
  /**
@@ -1,11 +1,12 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { mkdir } from 'node:fs/promises';
3
- import * as mcpBridge from './mcp-client/bridge.js';
4
- import { toMcpMounts } from './mcp-config.js';
3
+ import * as mcpBridge from './bridge/bridge.js';
4
+ import { toMcpMounts } from '../../application/mcp/mcp-config.js';
5
5
  import { mcpCredentialResolver } from './mcp-credentials.js';
6
- import { SerialPassQueue, RetryScheduler } from './mount-lifecycle.js';
7
- import { qualifiedSuiteId, suiteDataDir } from '../catalog/paths.js';
8
- import { redactErrorMessage } from './mcp-redaction.js';
6
+ import { SerialPassQueue, RetryScheduler } from '../core/mount-lifecycle.js';
7
+ import { qualifiedSuiteId, suiteDataDir } from '../../catalog/paths.js';
8
+ import { redactErrorMessage } from '../../application/mcp/mcp-redaction.js';
9
+ import { causeMessages } from '../host/failure-detail.js';
9
10
  export class McpMountRegistry {
10
11
  ctx;
11
12
  pluginDataRoot;
@@ -157,7 +158,13 @@ export class McpMountRegistry {
157
158
  // reported so a transient error does not shadow the final state.
158
159
  const failure = await this.mountWith(entry.request);
159
160
  if (failure !== undefined) {
160
- diagnostics.push({ suiteId: qualifiedSuiteId(entry.suite.sourceId, entry.suite.id), serverKey: entry.serverKey, reason: failure.reason, code: failure.code });
161
+ diagnostics.push({
162
+ suiteId: qualifiedSuiteId(entry.suite.sourceId, entry.suite.id),
163
+ serverKey: entry.serverKey,
164
+ reason: failure.reason,
165
+ code: failure.code,
166
+ ...(failure.causes === undefined || failure.causes.length === 0 ? {} : { causes: failure.causes })
167
+ });
161
168
  // Foreign and duplicate skips are deterministic, not transient:
162
169
  // retrying them just burns the attempt budget and log lines. The
163
170
  // next full reconcile re-checks them anyway, so the self-heal path
@@ -263,7 +270,7 @@ export class McpMountRegistry {
263
270
  // Ignore teardown errors: the startup failure is the real signal.
264
271
  }
265
272
  }
266
- return { reason: `mount failed: ${redactErrorMessage(error instanceof Error ? error.message : String(error))}`, code: 'mount-failed' };
273
+ return { reason: `mount failed: ${redactErrorMessage(error instanceof Error ? error.message : String(error))}`, code: 'mount-failed', causes: causeMessages(error) };
267
274
  }
268
275
  this.live.set(mountKey(request.suiteId, request.serverKey), {
269
276
  suiteId: request.suiteId,
@@ -10,9 +10,9 @@
10
10
  * @module runtime/user-commands
11
11
  */
12
12
  import { createUserMessage } from '@deepseek-ai/dsh-llm';
13
- import { commandCallName } from '../model/command-names.js';
14
- import { USER_ENTRY_PATH } from './user-store.js';
15
- import { pluginMarketSource } from './plugin-message-source.js';
13
+ import { commandCallName } from '../../model/command-names.js';
14
+ import { USER_ENTRY_PATH } from '../../application/panels/user-store.js';
15
+ import { pluginMarketSource } from '../host/plugin-message-source.js';
16
16
  /** Register/unregister user-panel commands to match the panel's enabled entries. */
17
17
  export class UserCommandMountRegistry {
18
18
  ctx;
@@ -30,8 +30,8 @@
30
30
  * @module runtime/user-panels
31
31
  */
32
32
  import { dirname } from 'node:path';
33
- import { skillEntryRejection } from '../catalog/skills-parse.js';
34
- import { deleteEntryDocument, entryExists, listEntryDocuments, listEntryFiles, readEntryDocument, resolveEntryDocument, SKILL_ENTRY_SHAPES, USER_ENTRY_NAME, USER_ENTRY_PATH, userEntryDir, writeEntryDocument, writeEntryFile } from './user-store.js';
33
+ import { skillEntryRejection } from '../../catalog/skills-parse.js';
34
+ import { deleteEntryDocument, entryExists, listEntryDocuments, listEntryFiles, readEntryDocument, resolveEntryDocument, SKILL_ENTRY_SHAPES, USER_ENTRY_NAME, USER_ENTRY_PATH, userEntryDir, writeEntryDocument, writeEntryFile } from '../../application/panels/user-store.js';
35
35
  /** The skill-source label user-panel entries carry into the skill registry. */
36
36
  export const USER_PANEL_SKILL_SOURCE = 'user-panel';
37
37
  /**
@@ -1,13 +1,13 @@
1
1
  import { createUserMessage } from '@deepseek-ai/dsh-llm';
2
2
  import { parse as parseYaml } from 'yaml';
3
- import { stripFrontmatter } from '../catalog/skills-parse.js';
4
- import { parseFrontmatterRecord } from './user-store.js';
5
- import { qualifiedSuiteId, suiteDataDir } from '../catalog/paths.js';
6
- import { expandPluginPaths, pluginRootOf } from '../catalog/plugin-variables.js';
7
- import { defaultMarkdownResources, resourceText, resourceCommandName } from '../catalog/component-files.js';
8
- import { bindHostLocale } from './host-locale.js';
3
+ import { stripFrontmatter } from '../../catalog/skills-parse.js';
4
+ import { parseFrontmatterRecord } from '../../application/panels/user-store.js';
5
+ import { qualifiedSuiteId, suiteDataDir } from '../../catalog/paths.js';
6
+ import { expandPluginPaths, pluginRootOf } from '../../catalog/plugin-variables.js';
7
+ import { defaultMarkdownResources, resourceText, resourceCommandName } from '../../catalog/component-files.js';
8
+ import { bindHostLocale } from '../host/host-locale.js';
9
9
  import { injectDynamicContext, shellSeamOf } from './dynamic-context.js';
10
- import { pluginMarketSource } from './plugin-message-source.js';
10
+ import { pluginMarketSource } from '../host/plugin-message-source.js';
11
11
  const COMMAND_NAME = /^[a-z][a-z0-9_-]*$/;
12
12
  export class CommandMountRegistry {
13
13
  ctx;
@@ -18,8 +18,8 @@ import { writeFileAtomic } from '@deepseek-ai/dsh-atomic-write';
18
18
  import { tmpdir } from 'node:os';
19
19
  import { createHash } from 'node:crypto';
20
20
  import { join } from 'node:path';
21
- import { SerialPassQueue } from './mount-lifecycle.js';
22
- import { qualifiedSuiteId } from '../catalog/paths.js';
21
+ import { SerialPassQueue } from '../core/mount-lifecycle.js';
22
+ import { qualifiedSuiteId } from '../../catalog/paths.js';
23
23
  export class HooksMountRegistry {
24
24
  ctx;
25
25
  live = new Map();
@@ -1,11 +1,11 @@
1
1
  import { CommandMountRegistry } from './commands-mounts.js';
2
- import { expandPluginPaths, pluginRootOf } from '../catalog/plugin-variables.js';
3
- import { suiteDataDir } from '../catalog/paths.js';
4
- import { McpMountRegistry } from './mcp-mounts.js';
2
+ import { expandPluginPaths, pluginRootOf } from '../../catalog/plugin-variables.js';
3
+ import { suiteDataDir } from '../../catalog/paths.js';
4
+ import { McpMountRegistry } from '../mcp/mcp-mounts.js';
5
5
  import { HooksMountRegistry } from './hooks-mounts.js';
6
6
  import { readFile } from 'node:fs/promises';
7
- import { stripFrontmatter } from '../catalog/skills-parse.js';
8
- import { parseFrontmatterRecord } from './user-store.js';
7
+ import { stripFrontmatter } from '../../catalog/skills-parse.js';
8
+ import { parseFrontmatterRecord } from '../../application/panels/user-store.js';
9
9
  /** Mount under existing and newly created agents; the caller owns refresh and teardown. */
10
10
  export function mountProjectCommands(ctx, catalog, t, dataRoot) {
11
11
  return mountProjectSurface(ctx, catalog, 'commands', scope => new CommandMountRegistry(scope, t, dataRoot));
@@ -14,10 +14,10 @@
14
14
  * directory, so CC-authored skills work verbatim under the harness.
15
15
  */
16
16
  import { readFile } from 'node:fs/promises';
17
- import { parseSkillFrontmatter, stripFrontmatter } from '../catalog/skills-parse.js';
18
- import { parseFrontmatterRecord } from './user-store.js';
19
- import { expandPluginPaths, pluginRootOf } from '../catalog/plugin-variables.js';
20
- import { suiteDataDir } from '../catalog/paths.js';
17
+ import { parseSkillFrontmatter, stripFrontmatter } from '../../catalog/skills-parse.js';
18
+ import { parseFrontmatterRecord } from '../../application/panels/user-store.js';
19
+ import { expandPluginPaths, pluginRootOf } from '../../catalog/plugin-variables.js';
20
+ import { suiteDataDir } from '../../catalog/paths.js';
21
21
  import { injectDynamicContext } from './dynamic-context.js';
22
22
  export const SUITE_PROJECT_SOURCE = 'agent-plugin-project';
23
23
  export const SUITE_USER_SOURCE = 'agent-plugin-user';
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Panel identity is preserved so identically named cards from different suites remain addressable.
3
+ */
4
+ export interface AgentRoleEntry {
5
+ name: string;
6
+ path: string;
7
+ description: string;
8
+ disabled: boolean;
9
+ title?: string;
10
+ rawText?: string;
11
+ /** Suite checkout root the card's `${PLUGIN_ROOT}` variables resolve to; absent for project-native files. */
12
+ suiteRoot?: string;
13
+ /** The suite's `${PLUGIN_DATA}` directory; absent for project-native files. */
14
+ suiteData?: string;
15
+ }
16
+ /**
17
+ * The executable part of one card. `tools` / `disallowedTools` stay in the file
18
+ * and are preserved by the editors, but they are not applied.
19
+ */
20
+ export interface AgentRolePolicy {
21
+ content: string;
22
+ model?: string;
23
+ provider?: string;
24
+ reasoningEffort?: string;
25
+ title?: string;
26
+ description?: string;
27
+ disabled: boolean;
28
+ }
29
+ /** Strict YAML parsing prevents malformed routing metadata from silently inheriting wider privileges. */
30
+ export declare function parseAgentRole(text: string): AgentRolePolicy;
@@ -1,11 +1,10 @@
1
1
  import type { LspLegacySeamMigration, LspStatusPayload } from '../contracts/lsp-status.js';
2
2
  import type { OverviewPayload, ServerConfigPayload, SkillContent, SourceProgress, SuiteDetail } from '../contracts/market.js';
3
- import type { McpStatusPayload } from '../contracts/mcp-status.js';
3
+ import type { McpStatusPayload } from './mcp/mcp-status.js';
4
4
  import type { SourceRef, Suite, SuiteSurfaceKey } from '../model/types.js';
5
- import type { McpBackend } from '../runtime/mcp-backend.js';
6
- import { type McpImportResult } from '../runtime/mcp-direct-config.js';
7
- import type { McpMountDiagnostic } from '../runtime/mcp-mounts.js';
8
- import { type McpServerOverride, type McpSuiteOverrides } from '../runtime/mcp-overrides.js';
5
+ import type { McpBackend } from '../contracts/mcp.js';
6
+ import type { McpMountDiagnostic } from '../contracts/mcp.js';
7
+ import { type McpServerOverride, type McpSuiteOverrides } from './mcp/mcp-overrides.js';
9
8
  import { type CatalogGitOptions, type CatalogOptions } from './catalog-context.js';
10
9
  import { type LspServerTable, type McpBackendInfo, type SourceInput, type SourcePatch } from './ports.js';
11
10
  import { type CatalogSnapshot } from './snapshot-cache.js';
@@ -95,8 +94,6 @@ export declare class Catalog implements MarketService {
95
94
  mcpStatus(): Promise<McpStatusPayload>;
96
95
  /** Persist a user-owned MCP service and reconcile its bridge mount. */
97
96
  addMcpServer(name: string, server: unknown): Promise<void>;
98
- /** Import several pasted services in one write; each entry reports its own outcome. */
99
- importMcpServers(servers: unknown, overwrite: boolean): Promise<McpImportResult>;
100
97
  /** Read effective service configuration without exposing credential literals. */
101
98
  serverConfig(kind: 'mcp' | 'lsp', id: string): Promise<ServerConfigPayload>;
102
99
  /** Validate a complete replacement before writing; plugin checkouts remain untouched. */
@@ -1,6 +1,6 @@
1
1
  import type { SkillContent, SuiteDetail } from '../contracts/market.js';
2
2
  import { type InstalledEntry, type Suite } from '../model/types.js';
3
- import type { McpMountDiagnostic as McpDiagnostic } from '../runtime/mcp-mounts.js';
3
+ import type { McpMountDiagnostic as McpDiagnostic } from '../contracts/mcp.js';
4
4
  /** Build the detail response for one normalized suite. */
5
5
  export declare function buildSuiteDetail(suite: Suite, installed: InstalledEntry | undefined, diagnostics: readonly McpDiagnostic[], mcpOverrides?: Record<string, Record<string, unknown>>): Promise<SuiteDetail>;
6
6
  /** Read one skill body from a normalized suite. */
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Read one JSON document. Resolves `undefined` when the file is absent or its
3
+ * text is empty; a `parse` that wants an absent file to read as a default
4
+ * handles `undefined` itself.
5
+ */
6
+ export declare function readJsonFile(path: string): Promise<unknown>;
7
+ /**
8
+ * Write one JSON document atomically: two-space indent, trailing newline, and
9
+ * the private-file modes the plugin's state files carry (0o600 file, 0o700
10
+ * directory).
11
+ */
12
+ export declare function writeJsonDocument(path: string, document: unknown): Promise<void>;
@@ -1,4 +1,4 @@
1
- import type { LspServerSpec } from '../model/types.js';
1
+ import type { LspServerSpec } from '../../model/types.js';
2
2
  /** The direct LSP server configuration file path. */
3
3
  export declare function lspServersPath(agentsRoot: string): string;
4
4
  /** Load the user-configured LSP servers; unreadable or invalid files yield an empty table. */
@@ -12,9 +12,9 @@
12
12
  * Direct user-configured rows follow the same model under the sentinel suite
13
13
  * id `direct` (mirroring mcp-status's `plugin`/`direct` split).
14
14
  */
15
- import type { LspStatusPayload } from '../contracts/lsp-status.js';
16
- import type { LspMountDiagnostic } from './lsp-mounts.js';
17
- import type { LspServerSpec, Suite } from '../model/types.js';
15
+ import type { LspStatusPayload } from '../../contracts/lsp-status.js';
16
+ import type { LspMountDiagnostic } from '../../contracts/lsp.js';
17
+ import type { LspServerSpec, Suite } from '../../model/types.js';
18
18
  /** Sentinel suite id for user-configured (non-suite) LSP servers. */
19
19
  export declare const DIRECT_LSP_SUITE_ID = "direct";
20
20
  /** The subset of LspMountRegistry the aggregator consumes (structural, for tests). */