dsh-agent-plugins-market 0.9.2 → 0.11.0

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 (78) hide show
  1. package/README.md +6 -6
  2. package/README.zh.md +6 -6
  3. package/client/client.js +339 -223
  4. package/client/client.js.map +1 -1
  5. package/lib/packages/market-bundle/src/application/catalog.js +8 -0
  6. package/lib/packages/market-bundle/src/index.js +4 -4
  7. package/lib/packages/market-bundle/src/platform/settings-namespace.js +4 -16
  8. package/lib/packages/market-bundle/src/platform/settings-schema.js +6 -6
  9. package/lib/packages/market-bundle/src/routes.js +14 -0
  10. package/lib/packages/market-catalog/src/scanning/archive.js +31 -0
  11. package/lib/packages/market-catalog/src/scanning/validate.js +18 -1
  12. package/lib/packages/market-contracts/src/contracts/market.js +1 -0
  13. package/lib/packages/market-contracts/src/contracts/settings.js +20 -43
  14. package/lib/packages/market-contracts/src/ports/ports.js +1 -0
  15. package/lib/packages/market-mcp/src/application/mcp/mcp-direct-config.js +28 -0
  16. package/lib/packages/market-mcp/src/application/mcp/mcp-status.js +36 -8
  17. package/lib/packages/market-mcp/src/application/mcp-service.js +44 -3
  18. package/lib/packages/market-mcp/src/runtime/mcp/bridge/connection.js +42 -23
  19. package/lib/packages/market-mcp/src/runtime/mcp/bridge/oauth.js +29 -2
  20. package/lib/packages/market-mcp/src/runtime/mcp/bridge/transport.js +8 -5
  21. package/lib/packages/market-runtime/src/runtime/agents/agent-role-router.js +2 -1
  22. package/lib/packages/market-runtime/src/runtime/agents/subagent-seam.js +29 -0
  23. package/lib/packages/market-runtime/src/runtime/agents/teammate-role-runtime.js +8 -2
  24. package/lib/packages/market-runtime/src/runtime/core/reconciler.js +12 -0
  25. package/lib/packages/market-runtime/src/runtime/host/scoped-contributors.js +4 -0
  26. package/lib/packages/market-runtime/src/runtime/surfaces/project-runtime.js +4 -1
  27. package/lib/types/packages/market-bundle/src/application/catalog.d.ts +6 -0
  28. package/lib/types/packages/market-bundle/src/index.d.ts +1 -1
  29. package/lib/types/packages/market-bundle/src/platform/settings-namespace.d.ts +4 -18
  30. package/lib/types/packages/market-bundle/src/platform/settings-schema.d.ts +6 -6
  31. package/lib/types/packages/market-contracts/src/contracts/market.d.ts +1 -0
  32. package/lib/types/packages/market-contracts/src/contracts/mcp-status.d.ts +16 -0
  33. package/lib/types/packages/market-contracts/src/contracts/settings.d.ts +15 -39
  34. package/lib/types/packages/market-contracts/src/ports/ports.d.ts +11 -0
  35. package/lib/types/packages/market-contracts/src/ports/queries.d.ts +2 -0
  36. package/lib/types/packages/market-mcp/src/application/mcp/mcp-direct-config.d.ts +12 -0
  37. package/lib/types/packages/market-mcp/src/application/mcp/mcp-status.d.ts +8 -2
  38. package/lib/types/packages/market-mcp/src/application/mcp-service.d.ts +12 -0
  39. package/lib/types/packages/market-mcp/src/runtime/mcp/bridge/connection.d.ts +4 -1
  40. package/lib/types/packages/market-mcp/src/runtime/mcp/bridge/oauth.d.ts +14 -0
  41. package/lib/types/packages/market-mcp/src/runtime/mcp/bridge/transport.d.ts +5 -1
  42. package/lib/types/packages/market-runtime/src/runtime/agents/agent-role-router.d.ts +2 -10
  43. package/lib/types/packages/market-runtime/src/runtime/agents/subagent-seam.d.ts +63 -0
  44. package/lib/types/packages/market-runtime/src/runtime/core/reconciler.d.ts +13 -0
  45. package/package.json +67 -62
  46. package/schemas/1.0.0/mcp.schema.json +0 -120
  47. package/schemas/1.0.0/plugin.schema.json +0 -65
  48. package/schemas/1.1.0/mcp.schema.json +0 -120
  49. package/schemas/1.1.0/plugin.schema.json +0 -65
  50. package/schemas/README.md +0 -79
  51. package/schemas/agent-plugins/spec.md +0 -62
  52. package/schemas/claude-code/marketplace.schema.json +0 -143
  53. package/schemas/claude-code/plugin.schema.json +0 -105
  54. package/schemas/claude-code/spec.md +0 -108
  55. package/schemas/codex/marketplace.schema.json +0 -78
  56. package/schemas/codex/plugin.schema.json +0 -77
  57. package/schemas/codex/spec.md +0 -91
  58. package/schemas/com.deepseek.harness/namespace.schema.json +0 -42
  59. package/schemas/com.deepseek.harness/spec.md +0 -52
  60. package/schemas/cursor/marketplace.schema.json +0 -82
  61. package/schemas/cursor/plugin.schema.json +0 -58
  62. package/schemas/cursor/spec.md +0 -92
  63. package/schemas/github-copilot/marketplace.schema.json +0 -107
  64. package/schemas/github-copilot/plugin.schema.json +0 -89
  65. package/schemas/github-copilot/spec.md +0 -105
  66. package/schemas/kimi/marketplace.schema.json +0 -38
  67. package/schemas/kimi/plugin.schema.json +0 -84
  68. package/schemas/kimi/spec.md +0 -94
  69. package/schemas/qoder/marketplace.schema.json +0 -115
  70. package/schemas/qoder/plugin.schema.json +0 -127
  71. package/schemas/qoder/spec.md +0 -111
  72. package/schemas/skill-collection/spec.md +0 -37
  73. package/schemas/universal/marketplace.schema.json +0 -98
  74. package/schemas/universal/plugin.schema.json +0 -49
  75. package/schemas/universal/spec.md +0 -53
  76. package/schemas/zcode/marketplace.schema.json +0 -161
  77. package/schemas/zcode/plugin.schema.json +0 -89
  78. package/schemas/zcode/spec.md +0 -102
@@ -132,6 +132,14 @@ export class LoopbackOAuthClientProvider {
132
132
  expectedState;
133
133
  /** Resolves `true` when the leg saved tokens, `false` when it failed; undefined while no leg is open. */
134
134
  browserLegSettled;
135
+ /**
136
+ * Outcome of the most recently finished browser leg, `true` when the code
137
+ * exchange saved tokens. The pending-leg promise disappears when the leg
138
+ * settles, and a leg that finishes inside the `connect` that raised the
139
+ * `401` leaves nothing pending for a supervisor to observe — this field is
140
+ * how the supervisor still learns that the authorization succeeded.
141
+ */
142
+ lastLegOutcome;
135
143
  callbackServer;
136
144
  callbackTimer;
137
145
  boundPort;
@@ -149,7 +157,10 @@ export class LoopbackOAuthClientProvider {
149
157
  get redirectUrl() {
150
158
  // RFC 8252 §7.3: loopback redirects may vary the port per request, so the
151
159
  // bound port is filled in when the listener starts and registration
152
- // metadata always reflects the current attempt's URL.
160
+ // metadata always reflects the current attempt's URL. The value must keep
161
+ // reading as the bound port until the code exchange finishes: the token
162
+ // request repeats this exact URL (RFC 6749 §4.1.3), and an authorization
163
+ // server that compares it with the authorization request rejects port `0`.
153
164
  return `http://127.0.0.1:${this.boundPort ?? this.storage.callbackPort ?? 0}/callback`;
154
165
  }
155
166
  get clientMetadata() {
@@ -214,6 +225,12 @@ export class LoopbackOAuthClientProvider {
214
225
  return;
215
226
  }
216
227
  finally {
228
+ // The loopback URL stays published until here. Clearing the port when
229
+ // the callback arrives would hand the token endpoint `redirect_uri =
230
+ // http://127.0.0.1:0/callback` while the authorization request carried
231
+ // the real port, which RFC 6749 §4.1.3 forbids and servers enforce.
232
+ this.boundPort = undefined;
233
+ this.lastLegOutcome = succeeded;
217
234
  this.browserLegSettled?.resolve(succeeded);
218
235
  this.browserLegSettled = undefined;
219
236
  }
@@ -232,6 +249,14 @@ export class LoopbackOAuthClientProvider {
232
249
  get browserLeg() {
233
250
  return this.browserLegSettled?.promise;
234
251
  }
252
+ /**
253
+ * The most recent finished leg's outcome; `undefined` until one finishes.
254
+ * The supervisor reads it after a failed attempt: a leg that settled inside
255
+ * the attempt itself leaves no pending {@link browserLeg} behind.
256
+ */
257
+ get lastBrowserLegOutcome() {
258
+ return this.lastLegOutcome;
259
+ }
235
260
  /**
236
261
  * Register the transport generation whose `finishAuth` completes the flow.
237
262
  * The transport calls `redirectToAuthorization` during `connect`; binding
@@ -420,7 +445,9 @@ export class LoopbackOAuthClientProvider {
420
445
  }
421
446
  this.clearCallbackTimer();
422
447
  this.callbackServer = undefined;
423
- this.boundPort = undefined;
448
+ // The bound port deliberately survives this callback: the code exchange
449
+ // runs a moment later and must present the same `redirect_uri` the
450
+ // authorization request used. An aborted leg clears it in resetCallback.
424
451
  this.expectedState = undefined;
425
452
  if (oauthError !== null) {
426
453
  const description = redactErrorMessage(url.searchParams.get('error_description') ?? '');
@@ -33,11 +33,11 @@ function hasStaticAuthorization(headers) {
33
33
  * when the config opts out or the headers already carry a static
34
34
  * Authorization.
35
35
  */
36
- function buildOAuthProvider(config, credentials, log) {
36
+ function buildOAuthProvider(config, credentials, log, opener) {
37
37
  const auth = config.auth;
38
38
  if (auth?.enabled === false || hasStaticAuthorization(config.headers))
39
39
  return undefined;
40
- return new LoopbackOAuthClientProvider(config.serverName, auth?.storage ?? {}, auth?.scope, log, credentials);
40
+ return new LoopbackOAuthClientProvider(config.serverName, auth?.storage ?? {}, auth?.scope, log, credentials, opener);
41
41
  }
42
42
  /**
43
43
  * Create an MCP transport from the resolved bridge config.
@@ -54,11 +54,14 @@ function buildOAuthProvider(config, credentials, log) {
54
54
  * @param credentials - The composition's credential store, when mounted; OAuth
55
55
  * state persists only when it is.
56
56
  * @param log - Diagnostic sink for the human leg of the flow.
57
+ * @param opener - Browser opener for the OAuth human leg; the platform browser
58
+ * by default. A caller that owns the human leg (a host surface, or a test
59
+ * driving the authorization endpoint directly) passes its own.
57
60
  * @returns The transport plus its OAuth provider, when active — the
58
61
  * supervisor queries the provider to hold the generation open while a
59
62
  * browser leg is pending.
60
63
  */
61
- export function createTransport(config, credentials, log = () => { }) {
64
+ export function createTransport(config, credentials, log = () => { }, opener) {
62
65
  switch (config.transport) {
63
66
  case 'stdio':
64
67
  return {
@@ -71,7 +74,7 @@ export function createTransport(config, credentials, log = () => { }) {
71
74
  oauthProvider: undefined
72
75
  };
73
76
  case 'streamable-http': {
74
- const oauthProvider = buildOAuthProvider(config, credentials, log);
77
+ const oauthProvider = buildOAuthProvider(config, credentials, log, opener);
75
78
  // The MCP SDK's StreamableHTTPClientTransport has optional callback
76
79
  // properties typed without `| undefined` (exactOptionalPropertyTypes
77
80
  // mismatch with the Transport interface); the SDK constructed the
@@ -86,7 +89,7 @@ export function createTransport(config, credentials, log = () => { }) {
86
89
  return { transport, oauthProvider };
87
90
  }
88
91
  case 'sse': {
89
- const oauthProvider = buildOAuthProvider(config, credentials, log);
92
+ const oauthProvider = buildOAuthProvider(config, credentials, log, opener);
90
93
  // The legacy SSE transport negotiates a separate endpoint POST channel.
91
94
  // The SDK applies `requestInit.headers` to both the stream request and
92
95
  // every endpoint POST (its `_commonHeaders` merge), so no separate
@@ -6,6 +6,7 @@ import { isAbsentPath } from '../../application/fs-probe.js';
6
6
  import { namedAgentRoles } from './agent-role-names.js';
7
7
  import { expandPluginPaths } from '../../../../market-catalog/src/index.js';
8
8
  import { parseAgentRole } from '../../application/agent-roles.js';
9
+ import { startContinuableChild } from './subagent-seam.js';
9
10
  export { parseAgentRole };
10
11
  export const AGENT_ROLE_TOOL_NAME = 'subagent_role';
11
12
  /**
@@ -217,7 +218,7 @@ export async function executeAgentRole(host, listRoles, agentName, prompt, paren
217
218
  ...(agentOptions === undefined ? {} : { agentOptions })
218
219
  };
219
220
  if (mode === 'continuable') {
220
- const started = await host.subagents.startContinuable({ provider: 'spawn', label: agentName, request, signal });
221
+ const started = await startContinuableChild(host.subagents, { provider: 'spawn', label: agentName, request, signal });
221
222
  return { kind: 'continuable', subagentId: started.childId };
222
223
  }
223
224
  const run = await host.subagents.start('spawn', { ...request, signal });
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Start one continuable child on whichever start member the host exposes.
3
+ * @param seam - the live `ctx.subagents` service.
4
+ * @param input - provider, label, delegation request, and cancellation.
5
+ * @returns the child identity the host reserved.
6
+ * @throws when the host exposes neither start member.
7
+ */
8
+ export function startContinuableChild(seam, input) {
9
+ if (typeof seam.startContinuable === 'function')
10
+ return seam.startContinuable(input);
11
+ if (typeof seam.startActivation === 'function')
12
+ return seam.startActivation({ ...input, delivery: 'parent' });
13
+ return Promise.reject(new Error('the host subagent service exposes neither startContinuable nor startActivation'));
14
+ }
15
+ /**
16
+ * Release selected resident continuable children on whichever drain member the
17
+ * host exposes.
18
+ * @param seam - the live `ctx.subagents` service.
19
+ * @param parent - exact live direct parent authorizing the release.
20
+ * @param childIds - durable direct-child ids to release when resident.
21
+ * @throws when the host exposes neither drain member.
22
+ */
23
+ export function drainContinuableChildIds(seam, parent, childIds) {
24
+ if (typeof seam.drainContinuableChildren === 'function')
25
+ return seam.drainContinuableChildren(parent, childIds);
26
+ if (typeof seam.drainChildren === 'function')
27
+ return seam.drainChildren(parent, childIds);
28
+ return Promise.reject(new Error('the host subagent service exposes neither drainContinuableChildren nor drainChildren'));
29
+ }
@@ -4,6 +4,7 @@ import { installModelSelection } from '@deepseek-ai/dsh-agent';
4
4
  import { createUserMessage } from '@deepseek-ai/dsh-llm';
5
5
  import { foldSubagentDescriptor } from '@deepseek-ai/dsh-subagent';
6
6
  import { z } from 'zod';
7
+ import { drainContinuableChildIds } from './subagent-seam.js';
7
8
  const PROVIDER = 'market-role-team';
8
9
  export const TEAM_ROLE_SOURCE = 'teammate-role-binding';
9
10
  const routeSchema = z.object({ provider: z.string().min(1), model: z.string().min(1), reasoningEffort: z.string().min(1).optional() }).strict();
@@ -79,10 +80,15 @@ export class TeammateRoleRuntime {
79
80
  this.ctx = ctx;
80
81
  this.teams = ctx.get('agentTeams');
81
82
  this.subagents = ctx.get('subagents');
83
+ // The 0.2.1-alpha.4 host removed `toolFilter` and `persona` from the capability
84
+ // record and added `agentPreset`; the 0.2.0-rc.2 baseline types require the first
85
+ // two. One variable satisfies both shapes because the host reads only the flags
86
+ // it knows, while an object literal would fail the newer excess-property check.
87
+ const capabilities = { agentOptions: false, agentPreset: false, outputSchema: false, depthLimit: false, toolFilter: false, persona: false };
82
88
  this.registrations.push(this.subagents.registerProvider({
83
89
  name: PROVIDER,
84
90
  inheritsParentContext: false,
85
- capabilities: { agentOptions: false, outputSchema: false, depthLimit: false, toolFilter: false, persona: false },
91
+ capabilities,
86
92
  start: () => Promise.reject(new Error('role teammates require Team continuation')),
87
93
  prepareContinuable: request => {
88
94
  const pending = this.pending.getStore();
@@ -227,7 +233,7 @@ export class TeammateRoleRuntime {
227
233
  byParent.set(parent, [...(byParent.get(parent) ?? []), agent.id]);
228
234
  }
229
235
  try {
230
- await Promise.all([...byParent].map(([parent, ids]) => this.subagents.drainContinuableChildren(parent, ids)));
236
+ await Promise.all([...byParent].map(([parent, ids]) => drainContinuableChildIds(this.subagents, parent, ids)));
231
237
  }
232
238
  finally {
233
239
  for (const off of this.registrations.splice(0).reverse())
@@ -111,6 +111,18 @@ export class RuntimeReconciler {
111
111
  mcpServerOwner(serverName) {
112
112
  return this.mcp.serverOwner(serverName);
113
113
  }
114
+ /**
115
+ * The suite/server pairs a live session's selection keeps mounted.
116
+ *
117
+ * This is the demand the mount pass merged in as `enabled: true`, so a server
118
+ * listed here stays live however its stored override reads. The MCP status
119
+ * surface needs it to tell a server its user switched off — kept mounted by
120
+ * that user's own preset — from one whose teardown genuinely failed.
121
+ * @returns the demanded pairs, in suite-then-server order.
122
+ */
123
+ demandedMcpMounts() {
124
+ return [...this.demandedMcp].flatMap(([suiteId, keys]) => [...keys].map(serverKey => ({ suiteId, serverKey })));
125
+ }
114
126
  /** Publish fresh immutable authority without mounting or waiting behind network work. */
115
127
  setCatalogAuthority(candidates, global) {
116
128
  if (this.disposed)
@@ -98,6 +98,10 @@ export class ScopedExtensionContributors {
98
98
  scope.systemPrompt.section({
99
99
  name: 'agent-plugins:instructions',
100
100
  order: 500,
101
+ // Suite-authored Markdown is literal text, never a template: the host
102
+ // interpolates by default and refuses an unknown `{{name}}`, so a suite
103
+ // that quotes `{{cwd}}` would fail prompt assembly before any request.
104
+ interpolate: false,
101
105
  text: () => mount.active
102
106
  ? mount.instructions
103
107
  .filter(row => this.ports.allows(agent, row.parent) && this.ports.allows(agent, row.id))
@@ -28,7 +28,10 @@ suiteAllowed) {
28
28
  return mountProjectSurface(ctx, catalog, 'systemPrompt', (scope, agent) => {
29
29
  let text = '';
30
30
  const host = scope;
31
- const dispose = host.systemPrompt.section({ name: 'agent-plugins:instructions', order: 500, text: () => text });
31
+ // Suite-authored Markdown is literal text, never a template: the host
32
+ // interpolates by default and refuses an unknown `{{name}}`, so a suite that
33
+ // quotes `{{cwd}}` would fail prompt assembly before any model request.
34
+ const dispose = host.systemPrompt.section({ name: 'agent-plugins:instructions', order: 500, interpolate: false, text: () => text });
32
35
  return {
33
36
  async reconcile(projectSuites) {
34
37
  const projectDir = agent.session.header.cwd;
@@ -181,6 +181,12 @@ export declare class Catalog implements MarketService {
181
181
  mcpStatus(): Promise<McpStatusPayload>;
182
182
  /** Validate and persist a user-owned MCP service with optional policy before reconciling its mount. */
183
183
  addMcpServer(name: string, server: unknown, policy?: unknown): Promise<void>;
184
+ /**
185
+ * Remove one user-owned MCP service, its override record, and its OAuth
186
+ * grant. A server a suite declares is refused: that declaration is
187
+ * source-owned, so disabling it is the supported operation.
188
+ */
189
+ removeMcpServer(suiteKey: string, serverKey: string): Promise<void>;
184
190
  /**
185
191
  * Read a service without credential literals, or an unsaved template when
186
192
  * `create` is true. The two transports have separate owners, so this facade
@@ -40,7 +40,7 @@ export interface Config {
40
40
  autoUpdateSources: Volatile<boolean | undefined>;
41
41
  /** UI translation switch; updated live through the host settings service. */
42
42
  translationEnabled: Volatile<boolean | undefined>;
43
- /** Agent preset manager switch (experimental); updated live through the host settings service. */
43
+ /** Agent preset manager switch; updated live through the host settings service. */
44
44
  agentPresetsEnabled: Volatile<boolean | undefined>;
45
45
  }
46
46
  /**
@@ -8,9 +8,7 @@
8
8
  * Every consumer reads through {@link MarketSettingsNamespace.settings} so
9
9
  * "the field is absent" has exactly one answer —
10
10
  * {@link resolveMarketSettings} — instead of each caller inventing its own
11
- * fallback for the window before the host applies its first value. That reader
12
- * also takes the interface language, because one field's default follows it
13
- * rather than a constant.
11
+ * fallback for the window before the host applies its first value.
14
12
  *
15
13
  * @module runtime/settings-namespace
16
14
  */
@@ -58,12 +56,6 @@ export declare class MarketSettingsNamespace {
58
56
  private readonly dataRoot;
59
57
  private readonly locale;
60
58
  private readonly host;
61
- /**
62
- * The host interface language, read per call. It is a reader rather than a
63
- * captured string because a language switch must move the translation
64
- * default without rebuilding this namespace.
65
- */
66
- private readonly localePreference;
67
59
  private readonly refs;
68
60
  private readonly watchers;
69
61
  private feedbackDisposer;
@@ -76,20 +68,14 @@ export declare class MarketSettingsNamespace {
76
68
  private feedbackMountState;
77
69
  constructor(ctx: Context, refs: MarketSettingRefs, dataRoot: string, locale: {
78
70
  t: HostTranslate;
79
- }, host: SettingsNamespaceHost,
80
- /**
81
- * The host interface language, read per call. It is a reader rather than a
82
- * captured string because a language switch must move the translation
83
- * default without rebuilding this namespace.
84
- */
85
- localePreference: () => string);
71
+ }, host: SettingsNamespaceHost);
86
72
  /** Subscribe the runtime reactions to live settings updates. */
87
73
  mount(): void;
88
74
  /** Release every watcher and unmount the feedback tool. */
89
75
  dispose(): void;
90
76
  /**
91
77
  * The resolved settings, from the volatile references the host updates in
92
- * place and the interface language they resolve over.
78
+ * place. A field whose reference has no value yet takes the contract default.
93
79
  */
94
80
  private settings;
95
81
  /** The persisted MCP backend choice; the built-in client until the host applies a value. */
@@ -104,7 +90,7 @@ export declare class MarketSettingsNamespace {
104
90
  downloadRegion(): Promise<DownloadRegionSetting>;
105
91
  /**
106
92
  * Read the live translation switch: the stored preference while the user has
107
- * one, the interface language's default otherwise.
93
+ * one, the plugin default (on) otherwise.
108
94
  */
109
95
  translationEnabled(): boolean;
110
96
  /**
@@ -5,12 +5,12 @@ import z from '@deepseek-ai/schemastery';
5
5
  * belong to {@link MARKET_SETTINGS_DEFAULTS}; this schema states them, it does
6
6
  * not own them.
7
7
  *
8
- * `translationEnabled` deliberately declares no default. Its default follows
9
- * the interface language, and a declared default would be materialized into the
10
- * live config reference, where "the user never set this" and "the user turned it
11
- * off" would both read `false` — the distinction the derivation rests on. With
12
- * the field absent, the reference answers `undefined` until the user writes it,
13
- * and {@link resolveMarketSettings} fills the language's own default in.
8
+ * `translationEnabled` deliberately declares no default. A declared default
9
+ * would be materialized into the live config reference, where "the user never
10
+ * set this" and "the user turned it off" would both read the same boolean, and
11
+ * the card could no longer tell the two states apart. With the field absent,
12
+ * the reference answers `undefined` until the user writes it, and
13
+ * {@link resolveMarketSettings} answers the plugin default for that case.
14
14
  */
15
15
  export declare const MarketSettingsFields: {
16
16
  readonly mcpEnhanced: z<boolean, boolean, "defined">;
@@ -8,6 +8,7 @@ export declare const MARKET_ROUTES: {
8
8
  readonly setSurfaceToggle: "/api/agent-plugins/surface-toggles/set";
9
9
  readonly mcpStatus: "/api/agent-plugins/mcp-status";
10
10
  readonly addMcpServer: "/api/agent-plugins/mcp-servers/add";
11
+ readonly removeMcpServer: "/api/agent-plugins/mcp-servers/remove";
11
12
  readonly lspStatus: "/api/agent-plugins/lsp-status";
12
13
  readonly lspServers: "/api/agent-plugins/lsp-servers";
13
14
  readonly addLspServer: "/api/agent-plugins/lsp-servers/add";
@@ -21,10 +21,26 @@ export type McpStatusState = 'connected' | 'degraded' | 'failed' | 'needs-creden
21
21
  export type McpStatusCode = 'unsupported-transport' | 'missing-credential' | 'credential-error' | 'unmount-failed' | 'mount-failed' | 'foreign-mount' | 'duplicate-mount'
22
22
  /** Tools are still registered although the surface that declared them is gone. */
23
23
  | 'orphaned-tools'
24
+ /** Switched off through an override record, and still mounted because the active session selection demands it. */
25
+ | 'preset-demand'
24
26
  /** Switched off through this plugin's own override record. */
25
27
  | 'disabled-override'
26
28
  /** A stored override changed the configuration the suite declares. */
27
29
  | 'modified-override';
30
+ /**
31
+ * One MCP suite/server pair a live session's selection keeps mounted.
32
+ *
33
+ * The mount pass merges session demand into the overrides as `enabled: true`,
34
+ * so a pair listed here is live whatever its stored switch says. The status
35
+ * surface reads the list to name that cause instead of reporting orphaned
36
+ * tools.
37
+ */
38
+ export interface McpDemandedMount {
39
+ /** Source-qualified suite id, as the status rows carry it. */
40
+ suiteId: string;
41
+ /** The server's key inside that suite's declaration. */
42
+ serverKey: string;
43
+ }
28
44
  /** One MCP service row for the status surface. */
29
45
  export interface McpStatusEntry {
30
46
  id: string;
@@ -9,11 +9,10 @@
9
9
  * resolved values off the settings snapshot it is handed. Changing a default is
10
10
  * therefore a one-line edit in this module.
11
11
  *
12
- * One default is not a constant: `translationEnabled` follows the interface
13
- * language, so its reader takes the language as an argument and
14
- * {@link interfaceLanguageTranslates} answers that default only. An explicit
15
- * preference is independent of language. {@link resolveTranslationTarget}
16
- * selects the language of displayed translations, including English.
12
+ * Every default is a plain boolean or word, so this module alone answers a
13
+ * section that says nothing. {@link resolveTranslationTarget} still selects the
14
+ * language translations are written in; that choice is independent of whether
15
+ * translations are displayed.
17
16
  *
18
17
  * @module contracts/settings
19
18
  */
@@ -33,13 +32,9 @@ export interface MarketSettings {
33
32
  feedbackEnabled: boolean;
34
33
  /** ON = every configured source refreshes in the background. */
35
34
  autoUpdateSources: boolean;
36
- /**
37
- * ON = suite, skill, command, agent, MCP and LSP text is localized for the
38
- * host locale. The stored value while the user has one, the interface
39
- * language's default otherwise.
40
- */
35
+ /** ON = suite, skill, command, agent, MCP and LSP text is localized for the host locale. */
41
36
  translationEnabled: boolean;
42
- /** ON = the composer carries the Agent preset manager entry (experimental). */
37
+ /** ON = the composer carries the Agent preset manager entry. */
43
38
  agentPresetsEnabled: boolean;
44
39
  }
45
40
  /**
@@ -48,14 +43,11 @@ export interface MarketSettings {
48
43
  * These are user-visible behavior: a flip here is a behavior change that ships
49
44
  * with its documentation, not a private implementation detail.
50
45
  *
51
- * `translationEnabled` is the one exception: the interface language decides it
52
- * (see {@link interfaceLanguageTranslates}), so this constant is not where its
53
- * effective default lives. It is what the browser store holds before the host
54
- * answers, and that pre-answer state deliberately withholds the translation
55
- * control rather than flashing it. The namespace schema declares no default for
56
- * the field at all, an absence that is load-bearing: a schema default would
57
- * reach the live config reference and erase the difference between "the user
58
- * never set this" and "the user turned it off".
46
+ * `translationEnabled` is the one field the namespace schema declares no
47
+ * default for. The field stays absent until the user writes it, which is what
48
+ * lets the card tell "never set" from an explicit value, and this constant
49
+ * answers the absent case. A schema default would reach the live config
50
+ * reference and erase that difference.
59
51
  */
60
52
  export declare const MARKET_SETTINGS_DEFAULTS: MarketSettings;
61
53
  /** Every field name the market's settings section carries. */
@@ -67,20 +59,6 @@ export type MarketSettingKey = keyof MarketSettings;
67
59
  * @param fallback - the value an unrecognized input reads as.
68
60
  */
69
61
  export declare function narrowDownloadRegion(value: unknown, fallback?: DownloadRegionSetting): DownloadRegionSetting;
70
- /**
71
- * Whether one interface language renders the market's Chinese dictionary.
72
- *
73
- * This is the host's own rule rather than a second reading of it:
74
- * `bindHostLocale` (`runtime/host/host-locale.ts`) answers every preference
75
- * that is not English with the Chinese dictionary, so as far as the interface is
76
- * concerned everything that is not English is Chinese — an absent preference
77
- * included, which the host reads as zh.
78
- *
79
- * This determines only the default display preference: Chinese starts on and
80
- * English starts off. It must not gate an explicitly enabled translation.
81
- * @param localePreference - the host `locale.preference`, or undefined while nothing supplies one.
82
- */
83
- export declare function interfaceLanguageTranslates(localePreference: string | undefined): boolean;
84
62
  /**
85
63
  * The language the interface renders, as the translation chain names it.
86
64
  *
@@ -111,11 +89,9 @@ export declare function resolveTranslationTarget(localePreference: string | unde
111
89
  * function is what keeps a second reading of "what does missing mean" from
112
90
  * growing somewhere else.
113
91
  *
114
- * A stored boolean always wins: the user's own answer is never overridden by
115
- * the language. Only a section that says nothing about the field takes the
116
- * language's answer, which is what makes a language switch move the default for
117
- * a user who never set the field and leave every other user alone.
92
+ * A stored value always wins: the user's own answer is never overridden by a
93
+ * default. Only a section that says nothing about the field takes the value in
94
+ * {@link MARKET_SETTINGS_DEFAULTS}.
118
95
  * @param section - the stored (or absent) namespace section, untrusted.
119
- * @param localePreference - the host `locale.preference` the language-derived default follows.
120
96
  */
121
- export declare function resolveMarketSettings(section: unknown, localePreference?: string): MarketSettings;
97
+ export declare function resolveMarketSettings(section: unknown): MarketSettings;
@@ -14,6 +14,7 @@ import type { MenuRowSource } from '../contracts/market.js';
14
14
  export type { McpBackendInfo } from '../contracts/market.js';
15
15
  import type { DownloadRegionSetting } from '../contracts/settings.js';
16
16
  import type { McpBackend } from '../contracts/mcp.js';
17
+ import type { McpDemandedMount } from '../contracts/mcp-status.js';
17
18
  import type { LspMountDiagnostic } from '../contracts/lsp.js';
18
19
  import type { DocumentTranslation, TranslationFields, TranslationProvider, TranslationSurfaceKind } from '../contracts/translation.js';
19
20
  /**
@@ -135,6 +136,16 @@ export interface CatalogPorts {
135
136
  suiteId: string;
136
137
  serverKey: string;
137
138
  } | undefined;
139
+ /**
140
+ * The suite/server pairs a live session's selection keeps mounted.
141
+ *
142
+ * Session demand is merged into the mount pass as `enabled: true`, so these
143
+ * servers stay live whatever their stored override says. The status surface
144
+ * reads them to report such a server as disabled and demanded rather than as
145
+ * tools left behind by a failed teardown. Optional: a composition with no
146
+ * session layer demands nothing, and the status reads exactly as before.
147
+ */
148
+ mcpDemandedMounts?: (() => readonly McpDemandedMount[]) | undefined;
138
149
  /** Latest LSP mount diagnostics. */
139
150
  lspStatusSource: LspMountStatusSource;
140
151
  /** The persisted MCP mount backend choice. */
@@ -43,6 +43,8 @@ export interface MarketQueries {
43
43
  export interface MarketMutations {
44
44
  saveServerConfig(kind: 'mcp' | 'lsp', id: string, config: unknown, policy?: unknown): Promise<void>;
45
45
  addMcpServer(name: string, server: unknown, policy?: unknown): Promise<void>;
46
+ /** Remove one user-owned MCP server declaration; a suite-owned server is refused. */
47
+ removeMcpServer(suiteKey: string, serverKey: string): Promise<void>;
46
48
  addLspServer(name: string, config: unknown): Promise<void>;
47
49
  addSource(input: SourceInput): Promise<SourceRef>;
48
50
  updateSource(sourceId: string, patch: SourcePatch): Promise<void>;
@@ -27,5 +27,17 @@ export declare function addUserMcpServer(agentsRoot: string, name: string, serve
27
27
  * once, within the same serialized mutation, after any policy has been saved.
28
28
  */
29
29
  export declare function prepareUserMcpServer(agentsRoot: string, name: string, server: unknown): Promise<() => Promise<void>>;
30
+ /**
31
+ * Validate a removal without publishing it. Call the returned atomic write once,
32
+ * within the same serialized mutation, after the plan is settled.
33
+ *
34
+ * The file is re-read rather than rebuilt from the parsed suite: rebuilding
35
+ * would drop top-level keys this client does not model and rewrite the other
36
+ * declarations into the parsed shape, while a removal must change exactly one
37
+ * key. A file the loader rejected is refused here for the same reason creation
38
+ * refuses it — the write would otherwise discard a declaration nothing could
39
+ * read.
40
+ */
41
+ export declare function prepareUserMcpServerRemoval(agentsRoot: string, name: string): Promise<() => Promise<void>>;
30
42
  /** The user's hand-written MCP declaration file: `<agentsRoot>/mcp.json`. */
31
43
  export declare function userMcpPath(agentsRoot: string): string;
@@ -1,5 +1,5 @@
1
1
  import type { McpSuiteOverrides } from './mcp-overrides.js';
2
- import type { McpStatusCode, McpStatusPayload } from '../../../../market-contracts/src/contracts/mcp-status.js';
2
+ import type { McpDemandedMount, McpStatusCode, McpStatusPayload } from '../../../../market-contracts/src/contracts/mcp-status.js';
3
3
  import type { Localization, McpToolSnapshot } from '../../../../market-contracts/src/ports/ports.js';
4
4
  import type { Suite } from '../../../../market-contracts/src/model/types.js';
5
5
  export type { McpStatusEntry, McpStatusPayload, McpStatusKind, McpStatusState } from '../../../../market-contracts/src/contracts/mcp-status.js';
@@ -35,6 +35,12 @@ export declare function declaresAuthHeader(config: Record<string, unknown> | und
35
35
  * resolver bound to it, when the deployment translates at all. Resolved once by
36
36
  * the caller: a server and each of its tools is localized from this one value,
37
37
  * never from a fresh read of the host preference.
38
+ * @param demanded - the suite/server pairs a live session's selection keeps
39
+ * mounted. The mount pass merged them in as `enabled: true`, so a stored
40
+ * `enabled: false` beside live tools has two possible causes: this demand, or
41
+ * a teardown that failed. Only the caller knows the demand, so it travels
42
+ * here; an omitted list means nothing is demanded and every such row keeps
43
+ * reading as an orphan.
38
44
  * @returns the status payload with every translatable field carrying its translation.
39
45
  */
40
- export declare function buildMcpStatus(suites: Suite[], diagnostics: McpDiagnostic[], observed: readonly McpToolSnapshot[], overrides?: Map<string, McpSuiteOverrides>, localization?: Localization): McpStatusPayload;
46
+ export declare function buildMcpStatus(suites: Suite[], diagnostics: McpDiagnostic[], observed: readonly McpToolSnapshot[], overrides?: Map<string, McpSuiteOverrides>, localization?: Localization, demanded?: readonly McpDemandedMount[]): McpStatusPayload;
@@ -24,6 +24,18 @@ export declare class McpService {
24
24
  * the previous override; the two files are not a cross-file transaction.
25
25
  */
26
26
  addServer(name: string, server: unknown, policy?: unknown): Promise<void>;
27
+ /**
28
+ * Remove one user-owned server from the declaration file and drop everything
29
+ * the plugin stored about it: the override record (edited config, enablement,
30
+ * policy) and the OAuth grant. Only the user's own declaration may be removed
31
+ * — a suite's `mcp.json` is source-owned, so a refresh would restore a server
32
+ * deleted from it, and disabling is the supported operation there.
33
+ *
34
+ * The declaration is written first. An interruption can then leave an inert
35
+ * override or grant record behind, never a live server whose stored policy was
36
+ * silently dropped.
37
+ */
38
+ removeServer(suiteKey: string, serverKey: string): Promise<void>;
27
39
  /** Read one MCP service without credential literals, or an unsaved creation template. */
28
40
  serverConfig(id: string, create?: boolean): Promise<ServerConfigPayload>;
29
41
  /**
@@ -20,6 +20,7 @@
20
20
  *
21
21
  * @module runtime/mcp-client/connection
22
22
  */
23
+ import type { BrowserOpener } from './oauth.js';
23
24
  import type { ToolHost } from './tools.js';
24
25
  import type { Config } from '../../../application/mcp/mcp-bridge-config.js';
25
26
  /** Automatic reconnect policy for one MCP server connection. */
@@ -69,6 +70,8 @@ export interface ConnectionHandle {
69
70
  * @param policy - Resolved reconnect policy from {@link resolveReconnectPolicy}.
70
71
  * @param credentials - The composition's credential store, when mounted; OAuth
71
72
  * state persists only when it is.
73
+ * @param opener - Browser opener for the OAuth human leg; the platform browser
74
+ * by default.
72
75
  * @returns Handle with a `ready` promise for startup-await and a `dispose` for teardown.
73
76
  */
74
- export declare function startConnection(host: ToolHost, config: Config, policy: ResolvedReconnectPolicy, credentials?: unknown): ConnectionHandle;
77
+ export declare function startConnection(host: ToolHost, config: Config, policy: ResolvedReconnectPolicy, credentials?: unknown, opener?: BrowserOpener): ConnectionHandle;
@@ -67,6 +67,14 @@ export declare class LoopbackOAuthClientProvider implements OAuthClientProvider
67
67
  private expectedState;
68
68
  /** Resolves `true` when the leg saved tokens, `false` when it failed; undefined while no leg is open. */
69
69
  private browserLegSettled;
70
+ /**
71
+ * Outcome of the most recently finished browser leg, `true` when the code
72
+ * exchange saved tokens. The pending-leg promise disappears when the leg
73
+ * settles, and a leg that finishes inside the `connect` that raised the
74
+ * `401` leaves nothing pending for a supervisor to observe — this field is
75
+ * how the supervisor still learns that the authorization succeeded.
76
+ */
77
+ private lastLegOutcome;
70
78
  private callbackServer;
71
79
  private callbackTimer;
72
80
  private boundPort;
@@ -98,6 +106,12 @@ export declare class LoopbackOAuthClientProvider implements OAuthClientProvider
98
106
  * settles.
99
107
  */
100
108
  get browserLeg(): Promise<boolean> | undefined;
109
+ /**
110
+ * The most recent finished leg's outcome; `undefined` until one finishes.
111
+ * The supervisor reads it after a failed attempt: a leg that settled inside
112
+ * the attempt itself leaves no pending {@link browserLeg} behind.
113
+ */
114
+ get lastBrowserLegOutcome(): boolean | undefined;
101
115
  /**
102
116
  * Register the transport generation whose `finishAuth` completes the flow.
103
117
  * The transport calls `redirectToAuthorization` during `connect`; binding