dsh-agent-plugins-market 0.10.0 → 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 (28) hide show
  1. package/client/client.js +223 -131
  2. package/client/client.js.map +1 -1
  3. package/lib/packages/market-bundle/src/application/catalog.js +8 -0
  4. package/lib/packages/market-bundle/src/index.js +4 -4
  5. package/lib/packages/market-bundle/src/platform/settings-namespace.js +4 -16
  6. package/lib/packages/market-bundle/src/platform/settings-schema.js +6 -6
  7. package/lib/packages/market-bundle/src/routes.js +14 -0
  8. package/lib/packages/market-contracts/src/contracts/market.js +1 -0
  9. package/lib/packages/market-contracts/src/contracts/settings.js +20 -43
  10. package/lib/packages/market-contracts/src/ports/ports.js +1 -0
  11. package/lib/packages/market-mcp/src/application/mcp/mcp-direct-config.js +28 -0
  12. package/lib/packages/market-mcp/src/application/mcp/mcp-status.js +36 -8
  13. package/lib/packages/market-mcp/src/application/mcp-service.js +44 -3
  14. package/lib/packages/market-runtime/src/runtime/core/reconciler.js +12 -0
  15. package/lib/types/packages/market-bundle/src/application/catalog.d.ts +6 -0
  16. package/lib/types/packages/market-bundle/src/index.d.ts +1 -1
  17. package/lib/types/packages/market-bundle/src/platform/settings-namespace.d.ts +4 -18
  18. package/lib/types/packages/market-bundle/src/platform/settings-schema.d.ts +6 -6
  19. package/lib/types/packages/market-contracts/src/contracts/market.d.ts +1 -0
  20. package/lib/types/packages/market-contracts/src/contracts/mcp-status.d.ts +16 -0
  21. package/lib/types/packages/market-contracts/src/contracts/settings.d.ts +15 -39
  22. package/lib/types/packages/market-contracts/src/ports/ports.d.ts +11 -0
  23. package/lib/types/packages/market-contracts/src/ports/queries.d.ts +2 -0
  24. package/lib/types/packages/market-mcp/src/application/mcp/mcp-direct-config.d.ts +12 -0
  25. package/lib/types/packages/market-mcp/src/application/mcp/mcp-status.d.ts +8 -2
  26. package/lib/types/packages/market-mcp/src/application/mcp-service.d.ts +12 -0
  27. package/lib/types/packages/market-runtime/src/runtime/core/reconciler.d.ts +13 -0
  28. package/package.json +1 -1
package/client/client.js CHANGED
@@ -851,11 +851,10 @@ function ActiveOverlay$1({ target, t, visible, slow }) {
851
851
  * resolved values off the settings snapshot it is handed. Changing a default is
852
852
  * therefore a one-line edit in this module.
853
853
  *
854
- * One default is not a constant: `translationEnabled` follows the interface
855
- * language, so its reader takes the language as an argument and
856
- * {@link interfaceLanguageTranslates} answers that default only. An explicit
857
- * preference is independent of language. {@link resolveTranslationTarget}
858
- * selects the language of displayed translations, including English.
854
+ * Every default is a plain boolean or word, so this module alone answers a
855
+ * section that says nothing. {@link resolveTranslationTarget} still selects the
856
+ * language translations are written in; that choice is independent of whether
857
+ * translations are displayed.
859
858
  *
860
859
  * @module contracts/settings
861
860
  */
@@ -867,14 +866,11 @@ const MARKET_SETTINGS_NAMESPACE = "dsh-agent-plugins-market";
867
866
  * These are user-visible behavior: a flip here is a behavior change that ships
868
867
  * with its documentation, not a private implementation detail.
869
868
  *
870
- * `translationEnabled` is the one exception: the interface language decides it
871
- * (see {@link interfaceLanguageTranslates}), so this constant is not where its
872
- * effective default lives. It is what the browser store holds before the host
873
- * answers, and that pre-answer state deliberately withholds the translation
874
- * control rather than flashing it. The namespace schema declares no default for
875
- * the field at all, an absence that is load-bearing: a schema default would
876
- * reach the live config reference and erase the difference between "the user
877
- * never set this" and "the user turned it off".
869
+ * `translationEnabled` is the one field the namespace schema declares no
870
+ * default for. The field stays absent until the user writes it, which is what
871
+ * lets the card tell "never set" from an explicit value, and this constant
872
+ * answers the absent case. A schema default would reach the live config
873
+ * reference and erase that difference.
878
874
  */
879
875
  const MARKET_SETTINGS_DEFAULTS = {
880
876
  mcpEnhanced: true,
@@ -882,8 +878,8 @@ const MARKET_SETTINGS_DEFAULTS = {
882
878
  downloadRegion: "auto",
883
879
  feedbackEnabled: true,
884
880
  autoUpdateSources: false,
885
- translationEnabled: false,
886
- agentPresetsEnabled: false
881
+ translationEnabled: true,
882
+ agentPresetsEnabled: true
887
883
  };
888
884
  /**
889
885
  * Narrow an untrusted stored region to a setting; anything else reads as the
@@ -899,22 +895,6 @@ function narrowBoolean(value, fallback) {
899
895
  return typeof value === "boolean" ? value : fallback;
900
896
  }
901
897
  /**
902
- * Whether one interface language renders the market's Chinese dictionary.
903
- *
904
- * This is the host's own rule rather than a second reading of it:
905
- * `bindHostLocale` (`runtime/host/host-locale.ts`) answers every preference
906
- * that is not English with the Chinese dictionary, so as far as the interface is
907
- * concerned everything that is not English is Chinese — an absent preference
908
- * included, which the host reads as zh.
909
- *
910
- * This determines only the default display preference: Chinese starts on and
911
- * English starts off. It must not gate an explicitly enabled translation.
912
- * @param localePreference - the host `locale.preference`, or undefined while nothing supplies one.
913
- */
914
- function interfaceLanguageTranslates(localePreference) {
915
- return localePreference === void 0 || !localePreference.toLowerCase().startsWith("en");
916
- }
917
- /**
918
898
  * Resolve a stored settings section the way the host resolves it: each field
919
899
  * takes its stored value when it is one this field accepts, and its default
920
900
  * otherwise.
@@ -925,14 +905,12 @@ function interfaceLanguageTranslates(localePreference) {
925
905
  * function is what keeps a second reading of "what does missing mean" from
926
906
  * growing somewhere else.
927
907
  *
928
- * A stored boolean always wins: the user's own answer is never overridden by
929
- * the language. Only a section that says nothing about the field takes the
930
- * language's answer, which is what makes a language switch move the default for
931
- * a user who never set the field and leave every other user alone.
908
+ * A stored value always wins: the user's own answer is never overridden by a
909
+ * default. Only a section that says nothing about the field takes the value in
910
+ * {@link MARKET_SETTINGS_DEFAULTS}.
932
911
  * @param section - the stored (or absent) namespace section, untrusted.
933
- * @param localePreference - the host `locale.preference` the language-derived default follows.
934
912
  */
935
- function resolveMarketSettings(section, localePreference) {
913
+ function resolveMarketSettings(section) {
936
914
  const stored = typeof section === "object" && section !== null ? section : {};
937
915
  return {
938
916
  mcpEnhanced: narrowBoolean(stored["mcpEnhanced"], MARKET_SETTINGS_DEFAULTS.mcpEnhanced),
@@ -940,7 +918,7 @@ function resolveMarketSettings(section, localePreference) {
940
918
  downloadRegion: narrowDownloadRegion(stored["downloadRegion"]),
941
919
  feedbackEnabled: narrowBoolean(stored["feedbackEnabled"], MARKET_SETTINGS_DEFAULTS.feedbackEnabled),
942
920
  autoUpdateSources: narrowBoolean(stored["autoUpdateSources"], MARKET_SETTINGS_DEFAULTS.autoUpdateSources),
943
- translationEnabled: narrowBoolean(stored["translationEnabled"], interfaceLanguageTranslates(localePreference)),
921
+ translationEnabled: narrowBoolean(stored["translationEnabled"], MARKET_SETTINGS_DEFAULTS.translationEnabled),
944
922
  agentPresetsEnabled: narrowBoolean(stored["agentPresetsEnabled"], MARKET_SETTINGS_DEFAULTS.agentPresetsEnabled)
945
923
  };
946
924
  }
@@ -973,6 +951,7 @@ const MARKET_ROUTES = {
973
951
  setSurfaceToggle: `${MARKET_API_PREFIX}surface-toggles/set`,
974
952
  mcpStatus: `${MARKET_API_PREFIX}mcp-status`,
975
953
  addMcpServer: `${MARKET_API_PREFIX}mcp-servers/add`,
954
+ removeMcpServer: `${MARKET_API_PREFIX}mcp-servers/remove`,
976
955
  lspStatus: `${MARKET_API_PREFIX}lsp-status`,
977
956
  lspServers: `${MARKET_API_PREFIX}lsp-servers`,
978
957
  addLspServer: `${MARKET_API_PREFIX}lsp-servers/add`,
@@ -1510,6 +1489,23 @@ async function addMcpServer(name, config, policy) {
1510
1489
  });
1511
1490
  });
1512
1491
  }
1492
+ /**
1493
+ * Remove one user-owned MCP server from its declaration file.
1494
+ *
1495
+ * A suite-declared server belongs to its source, so the backend refuses it;
1496
+ * only the `@user-mcp` declaration is the user's to delete.
1497
+ *
1498
+ * @param suiteId - the source-qualified suite id the status row carries.
1499
+ * @param serverKey - the server key inside that suite's declaration.
1500
+ */
1501
+ async function removeMcpServer(suiteId, serverKey) {
1502
+ return withBusyOperation(async () => {
1503
+ await postAction("mcp-servers/remove", {
1504
+ suiteId,
1505
+ serverKey
1506
+ });
1507
+ });
1508
+ }
1513
1509
  /** Drop one server's OAuth grant so its next mount re-runs browser authorization. */
1514
1510
  async function reauthorizeMcpServer(serverName) {
1515
1511
  return withBusyOperation(async () => {
@@ -2061,6 +2057,10 @@ const zh = {
2061
2057
  mcpRetryPreservesCredentials: "重新尝试连接失败的托管服务;保留已有凭据。此操作会检查所有托管 MCP 服务。",
2062
2058
  mcpReauthExplain: "这将清除本服务已保存的 OAuth 授权并重新连接,可能中断正在使用它的请求。仅在需要更换账号或授权权限时使用;若服务要求登录,请在打开的浏览器页面完成授权。",
2063
2059
  mcpConfirmReauth: "清除授权并重新连接",
2060
+ mcpDelete: "删除连接器",
2061
+ mcpDeleteTitle: "删除连接器「{name}」",
2062
+ mcpDeleteDesc: "该连接器的用户声明将被移除,此操作不可撤销。",
2063
+ mcpDeleteConfirm: "确认删除",
2064
2064
  mcpOauthDefault: "授权默认开启",
2065
2065
  mcpSaveFirst: "请先保存配置,再执行连接操作;连接操作只使用已保存的配置。",
2066
2066
  mcpStillUnavailable: "操作已执行,但服务仍未连接成功。",
@@ -2208,6 +2208,7 @@ const zh = {
2208
2208
  failureGuideUnmount: "卸载没有完成:宿主里可能还留着旧进程,重启宿主后再试。",
2209
2209
  failureGuideOrphanedTools: "声明它的套件已停用或卸载,工具却仍在注册表中:重启宿主即可清除。",
2210
2210
  failureGuideDisabledOverride: "已在配置覆写中停用这个服务。",
2211
+ failureGuidePresetDemand: "该服务由当前会话的预设选中,因此保持挂载;预设生效期间面板开关无法卸载它。",
2211
2212
  failureGuideModifiedOverride: "配置覆写改动了这个服务,实际运行的不是套件声明的原始配置。",
2212
2213
  panelAdd: "新增",
2213
2214
  panelEdit: "编辑",
@@ -2246,11 +2247,9 @@ const zh = {
2246
2247
  autoUpdateDesc: "每 6 小时刷新全部已配置来源。",
2247
2248
  feedbackToggleDesc: "Agent 遇到本插件的问题时可自动提交 issue 反馈。",
2248
2249
  translationToggleLabel: "自动翻译",
2249
- translationToggleExperimental: "实验性",
2250
- translationToggleDesc: "按界面语言显示描述与文档译文,名称保持原文。中文默认开启、英文默认关闭,手动选择优先。文档展开时按需翻译,支持原文、译文和双语对照;结果缓存在本地。",
2250
+ translationToggleDesc: "按界面语言显示描述与文档译文,名称保持原文。默认开启,手动选择优先。文档展开时按需翻译,支持原文、译文和双语对照;结果缓存在本地。",
2251
2251
  agentPresetsToggleLabel: "Agent 预设管理",
2252
- agentPresetsToggleExperimental: "实验性",
2253
- agentPresetsToggleDesc: "在会话输入框“权限”右侧显示 Agent 预设管理入口:按会话启用预设的能力集合。默认关闭。",
2252
+ agentPresetsToggleDesc: "在会话输入框“权限”右侧显示 Agent 预设管理入口:按会话启用预设的能力集合。默认开启。",
2254
2253
  translationShowOriginal: "显示原文",
2255
2254
  translationShowTranslated: "显示译文",
2256
2255
  translationViewLabel: "文档阅读模式",
@@ -2524,6 +2523,10 @@ const en = {
2524
2523
  mcpRetryPreservesCredentials: "Retries failed managed connections without clearing credentials. Checks all managed MCP services.",
2525
2524
  mcpReauthExplain: "This clears the saved OAuth grant for this service and reconnects it, which may interrupt active requests. Use it to change accounts or permissions. If sign-in is required, complete authorization in the browser page that opens.",
2526
2525
  mcpConfirmReauth: "Clear authorization and reconnect",
2526
+ mcpDelete: "Delete connector",
2527
+ mcpDeleteTitle: "Delete connector “{name}”",
2528
+ mcpDeleteDesc: "This removes the connector from the user MCP declaration. This cannot be undone.",
2529
+ mcpDeleteConfirm: "Delete",
2527
2530
  mcpOauthDefault: "OAuth on by default",
2528
2531
  mcpSaveFirst: "Save configuration before connection actions; they use saved configuration only.",
2529
2532
  mcpStillUnavailable: "The operation finished, but the service is still not connected.",
@@ -2671,6 +2674,7 @@ const en = {
2671
2674
  failureGuideUnmount: "Unmounting did not finish: the host may still hold the old process; restart the host and try again.",
2672
2675
  failureGuideOrphanedTools: "The suite that declared it is off or uninstalled, but its tools are still registered: restart the host to clear them.",
2673
2676
  failureGuideDisabledOverride: "This service is switched off in the configuration override.",
2677
+ failureGuidePresetDemand: "The active session preset selects this service, so it stays mounted; the panel switch cannot unmount it while that preset is active.",
2674
2678
  failureGuideModifiedOverride: "A configuration override changed this service; what runs is not the suite’s own declaration.",
2675
2679
  panelAdd: "Add",
2676
2680
  panelEdit: "Edit",
@@ -2709,11 +2713,9 @@ const en = {
2709
2713
  autoUpdateDesc: "Refreshes every configured source every 6 hours.",
2710
2714
  feedbackToggleDesc: "The agent may auto-file an issue when it hits a problem in this plugin.",
2711
2715
  translationToggleLabel: "Auto-translate",
2712
- translationToggleExperimental: "Experimental",
2713
- translationToggleDesc: "Shows descriptions and documents in the interface language; names stay as authored. On by default in Chinese and off in English, unless you choose otherwise. Documents translate on expansion with original, translation and bilingual views. Results are cached locally.",
2716
+ translationToggleDesc: "Shows descriptions and documents in the interface language; names stay as authored. On by default unless you choose otherwise. Documents translate on expansion with original, translation and bilingual views. Results are cached locally.",
2714
2717
  agentPresetsToggleLabel: "Agent preset manager",
2715
- agentPresetsToggleExperimental: "Experimental",
2716
- agentPresetsToggleDesc: "Shows the Agent preset manager entry beside Permissions in the composer: enable a preset per session. Off by default.",
2718
+ agentPresetsToggleDesc: "Shows the Agent preset manager entry beside Permissions in the composer: enable a preset per session. On by default.",
2717
2719
  translationShowOriginal: "Show original text",
2718
2720
  translationShowTranslated: "Show translated text",
2719
2721
  translationViewLabel: "Document reading mode",
@@ -3483,67 +3485,42 @@ function hasHan(text) {
3483
3485
  /**
3484
3486
  * Read-only translation preference shared by panels and menu integrations.
3485
3487
  * The host form owns persistence; this projection carries the resolved boolean
3486
- * — the user's stored value while they have one, the interface language's
3487
- * default otherwise — across late binding and namespace replacement. It is the
3488
- * display preference in both supported interface languages; target-language
3489
- * selection does not override an explicit on or off.
3488
+ * — the user's stored value while they have one, the plugin default otherwise
3489
+ * — across late binding and namespace replacement. The switch is on by default
3490
+ * in every interface language; target-language selection does not override an
3491
+ * explicit on or off.
3490
3492
  * @module client/ui/translation-enabled
3491
3493
  */
3492
- /** The settings entry the harness locale plugin owns; the node half names the same entry. */
3493
- const LOCALE_SETTINGS_ENTRY = "locale";
3494
- /**
3495
- * Read the interface language off the locale plugin's settings form.
3496
- *
3497
- * An absent preference reads as zh — the rule every host-facing read gives it
3498
- * (`runtime/host/host-locale.ts`), so the derived default cannot disagree with
3499
- * the language the market renders its own copy in.
3500
- * @param form - the host form for the `locale` entry.
3501
- * @returns the language source the derived default follows.
3502
- */
3503
- function bindInterfaceLanguage(form) {
3504
- return {
3505
- current: () => {
3506
- const preference = form.getSnapshot().value?.preference;
3507
- return typeof preference === "string" ? preference : "zh";
3508
- },
3509
- subscribe: (listener) => form.subscribe(listener)
3510
- };
3511
- }
3512
3494
  const state$2 = (0, _deepseek_ai_dsh_client_store.createSnapshotStore)(MARKET_SETTINGS_DEFAULTS.translationEnabled);
3513
3495
  let binding$1;
3514
3496
  let unsubscribeSource$1;
3515
- let unsubscribeLanguage;
3516
3497
  /** Shared read and subscription API. Consumers cannot write the preference. */
3517
3498
  const translationEnabled = {
3518
3499
  getSnapshot: () => state$2.getSnapshot(),
3519
3500
  subscribe: (listener) => state$2.subscribe(listener)
3520
3501
  };
3521
3502
  /**
3522
- * Whether translation is on for one stored section under one interface
3523
- * language: the user's stored boolean, else the language's default.
3503
+ * Whether translation is on for one stored section: the user's stored boolean,
3504
+ * else the plugin default the contract declares.
3524
3505
  * @param section - the host form's accepted section.
3525
- * @param language - the host interface language; absent keeps the declared default.
3526
3506
  */
3527
- function effectiveTranslationEnabled(section, language) {
3528
- return resolveMarketSettings(section, language).translationEnabled;
3507
+ function effectiveTranslationEnabled(section) {
3508
+ return resolveMarketSettings(section).translationEnabled;
3529
3509
  }
3530
3510
  /**
3531
- * Follow the host form and the interface language without adding persistence or
3532
- * polling. Replacing a binding disconnects its sources, not the existing
3533
- * consumers.
3511
+ * Follow the host form without adding persistence or polling. Replacing a
3512
+ * binding disconnects its source, not the existing consumers.
3534
3513
  *
3535
3514
  * A form that has not answered yet keeps the pre-answer default rather than
3536
- * resolving the absent section: the language-derived default describes a
3537
- * section the host has answered and deliberately left open, and publishing it
3538
- * before the answer would flash a control the user may have turned off. Only a
3539
- * defined value is resolved through the contract.
3515
+ * resolving the absent section: the declared default describes a section the
3516
+ * host has answered and deliberately left open, and reading an absent section
3517
+ * as one the document left open would report a state the document may not be
3518
+ * in. Only a defined value is resolved through the contract.
3540
3519
  * @param next - the host form for this plugin's settings namespace.
3541
- * @param language - the interface language the derived default follows; absent keeps the declared default.
3542
3520
  * @returns an idempotent disposer; an older disposer cannot clear a newer binding.
3543
3521
  */
3544
- function bindTranslationEnabled(next, language) {
3522
+ function bindTranslationEnabled(next) {
3545
3523
  unsubscribeSource$1?.();
3546
- unsubscribeLanguage?.();
3547
3524
  const current = {};
3548
3525
  binding$1 = current;
3549
3526
  const sync = () => {
@@ -3553,18 +3530,15 @@ function bindTranslationEnabled(next, language) {
3553
3530
  state$2.set(MARKET_SETTINGS_DEFAULTS.translationEnabled);
3554
3531
  return;
3555
3532
  }
3556
- state$2.set(effectiveTranslationEnabled(answered, language?.current()));
3533
+ state$2.set(effectiveTranslationEnabled(answered));
3557
3534
  };
3558
3535
  unsubscribeSource$1 = next.subscribe(sync);
3559
- unsubscribeLanguage = language?.subscribe(sync);
3560
3536
  sync();
3561
3537
  return () => {
3562
3538
  if (binding$1 !== current) return;
3563
3539
  binding$1 = void 0;
3564
3540
  unsubscribeSource$1?.();
3565
- unsubscribeLanguage?.();
3566
3541
  unsubscribeSource$1 = void 0;
3567
- unsubscribeLanguage = void 0;
3568
3542
  state$2.set(MARKET_SETTINGS_DEFAULTS.translationEnabled);
3569
3543
  };
3570
3544
  }
@@ -37695,6 +37669,7 @@ const FAILURE_GUIDANCE_LABEL = {
37695
37669
  unmount: "failureGuideUnmount",
37696
37670
  orphanedTools: "failureGuideOrphanedTools",
37697
37671
  disabledOverride: "failureGuideDisabledOverride",
37672
+ presetDemand: "failureGuidePresetDemand",
37698
37673
  modifiedOverride: "failureGuideModifiedOverride",
37699
37674
  foreignMount: "mcpForeignHint",
37700
37675
  duplicateMount: "mcpDuplicateHint"
@@ -37711,6 +37686,7 @@ function failureGuidanceKey(diagnostic) {
37711
37686
  if (code === "duplicate-mount") return "duplicateMount";
37712
37687
  if (code === "orphaned-tools") return "orphanedTools";
37713
37688
  if (code === "disabled-override") return "disabledOverride";
37689
+ if (code === "preset-demand") return "presetDemand";
37714
37690
  if (code === "modified-override") return "modifiedOverride";
37715
37691
  const text = [diagnostic.reason, ...diagnostic.causes ?? []].filter((line) => line !== void 0).join("\n");
37716
37692
  if (/does not support dynamic plugin mounting/i.test(text)) return "hostUnsupported";
@@ -38464,6 +38440,49 @@ function parseServerDocument(text, key) {
38464
38440
  };
38465
38441
  }
38466
38442
  /**
38443
+ * Read one document while a service is being created.
38444
+ *
38445
+ * Creating a service has no declaration key yet, so the pasted document decides
38446
+ * it: a keyed document names its own service and its key is adopted, while a
38447
+ * bare definition — the server object itself, no `mcpServers` seat — is read
38448
+ * under the draft key the name field carries. The strict reader above refuses
38449
+ * any document whose key differs from the declared one, which would trap the
38450
+ * create flow: the pasted key could never become the key of the new service.
38451
+ * The policy half is read the same way in both shapes.
38452
+ *
38453
+ * @param text - the document as the editor holds it.
38454
+ * @param draftKey - the key the name field currently carries, used by a bare definition.
38455
+ * @returns the service's key, definition, and policy.
38456
+ */
38457
+ function readDraftServerDocument(text, draftKey) {
38458
+ const parsed = JSON.parse(text);
38459
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("Configuration must be a JSON object");
38460
+ const record = parsed;
38461
+ const policies = asRecord(asRecord(record[HARNESS_NAMESPACE])?.["mcpServers"]);
38462
+ if (!("mcpServers" in record)) {
38463
+ const config = { ...record };
38464
+ if (policies !== void 0) delete config[HARNESS_NAMESPACE];
38465
+ return {
38466
+ key: draftKey,
38467
+ keyed: false,
38468
+ config,
38469
+ policy: asRecord(policies?.[draftKey]) ?? {}
38470
+ };
38471
+ }
38472
+ const servers = asRecord(record["mcpServers"]);
38473
+ const keys = servers === void 0 ? [] : Object.keys(servers);
38474
+ const key = keys[0];
38475
+ if (key === void 0 || keys.length !== 1) throw new Error("mcpServers must name exactly one service while a service is being created");
38476
+ const config = asRecord(servers?.[key]);
38477
+ if (config === void 0) throw new Error(`mcpServers must name a JSON object for the service key "${key}"`);
38478
+ return {
38479
+ key,
38480
+ keyed: true,
38481
+ config,
38482
+ policy: asRecord(policies?.[key]) ?? {}
38483
+ };
38484
+ }
38485
+ /**
38467
38486
  * Write the document back in the shape the specification seats it in. Without a
38468
38487
  * declaration key the definition is the whole document: wrapping it under an
38469
38488
  * empty `mcpServers` key would store a nested document as the service.
@@ -38526,7 +38545,7 @@ function ServerConfigEditor(props) {
38526
38545
  let parseError;
38527
38546
  try {
38528
38547
  if (props.kind === "mcp") {
38529
- const document = parseServerDocument(props.text, props.serverKey ?? "");
38548
+ const document = props.create === true ? readDraftServerDocument(props.text, props.serverKey ?? "") : parseServerDocument(props.text, props.serverKey ?? "");
38530
38549
  parsed = document.config;
38531
38550
  policy = document.policy;
38532
38551
  } else parsed = parseServerConfig(props.text);
@@ -38915,6 +38934,13 @@ function ServerConfigDetail({ kind, id, create = false, t, credentials, onSaved,
38915
38934
  (0, react.useEffect)(() => {
38916
38935
  onDirtyChange?.(dirty);
38917
38936
  }, [dirty, onDirtyChange]);
38937
+ /**
38938
+ * Read the document in the mode this editor is in. Creating adopts the key
38939
+ * the document names — the document is the source of truth there — while
38940
+ * editing keeps the declared key, because a renamed seat would silently
38941
+ * point the save at a different service.
38942
+ */
38943
+ const readDocument = (source) => create && kind === "mcp" ? readDraftServerDocument(source, serverKey) : parseServerDocument(source, serverKey);
38918
38944
  /** The document's policy half, as the string drafts the timeout fields edit. */
38919
38945
  const policyDraft = () => {
38920
38946
  if (kind !== "mcp" || text === void 0) return {
@@ -38922,7 +38948,7 @@ function ServerConfigDetail({ kind, id, create = false, t, credentials, onSaved,
38922
38948
  startupTimeoutMs: ""
38923
38949
  };
38924
38950
  try {
38925
- return policyDraftOfDocument(parseServerDocument(text, serverKey).policy);
38951
+ return policyDraftOfDocument(readDocument(text).policy);
38926
38952
  } catch {
38927
38953
  return {
38928
38954
  toolCallTimeoutMs: "",
@@ -38930,15 +38956,35 @@ function ServerConfigDetail({ kind, id, create = false, t, credentials, onSaved,
38930
38956
  };
38931
38957
  }
38932
38958
  };
38933
- /** Write the timeout drafts back into the document's policy half. */
38959
+ /**
38960
+ * Write the timeout drafts back into the document's policy half. The rewrite
38961
+ * composes under the current key and keeps the definition as read, so a bare
38962
+ * definition becomes keyed here rather than losing the policy seat.
38963
+ */
38934
38964
  const updatePolicy = (draft) => {
38935
38965
  if (text === void 0) return;
38936
38966
  try {
38937
- const document = parseServerDocument(text, serverKey);
38967
+ const document = readDocument(text);
38938
38968
  setText(composeServerDocument(serverKey, document.config, policyDocumentOfDraft(document.policy, draft)));
38939
38969
  } catch {}
38940
38970
  };
38941
38971
  /**
38972
+ * Take the document the editor just produced. While creating a service the
38973
+ * document names its own key: a pasted keyed document decides the name, so
38974
+ * the name field mirrors it instead of keeping a draft key the document no
38975
+ * longer uses. The document stays exactly as typed.
38976
+ */
38977
+ const changeText = (next) => {
38978
+ setText(next);
38979
+ if (!(create && kind === "mcp")) return;
38980
+ try {
38981
+ const draft = readDraftServerDocument(next, serverKey);
38982
+ if (!draft.keyed || draft.key === serverKey) return;
38983
+ setServerKey(draft.key);
38984
+ setName(draft.key);
38985
+ } catch {}
38986
+ };
38987
+ /**
38942
38988
  * Read the service again and rebuild every derived value from the response.
38943
38989
  * `silent` keeps the editor mounted for the read-back that follows a save, so
38944
38990
  * the user stays in the section they were editing. A service being created has
@@ -38989,7 +39035,7 @@ function ServerConfigDetail({ kind, id, create = false, t, credentials, onSaved,
38989
39035
  setFieldErrors(void 0);
38990
39036
  try {
38991
39037
  if (kind === "mcp") {
38992
- const document = parseServerDocument(text ?? "", serverKey);
39038
+ const document = readDocument(text ?? "");
38993
39039
  if (create) {
38994
39040
  const policy = policyRequestOfDocuments({}, document.policy);
38995
39041
  if (Object.keys(policy).length === 0) await addMcpServer(name.trim(), document.config);
@@ -39011,17 +39057,14 @@ function ServerConfigDetail({ kind, id, create = false, t, credentials, onSaved,
39011
39057
  };
39012
39058
  const saveable = valid && (create ? name.trim() !== "" : dirty);
39013
39059
  const nameCopy = NAME_COPY[kind];
39014
- let nameEditable = true;
39015
- if (create && kind === "mcp") try {
39016
- parseServerDocument(text ?? "", serverKey);
39017
- } catch {
39018
- nameEditable = false;
39019
- }
39020
39060
  const changeName = (next) => {
39021
- if (kind === "mcp" && next.trim() !== "" && text !== void 0) {
39022
- const document = parseServerDocument(text, serverKey);
39023
- setText(composeServerDocument(next.trim(), document.config, document.policy));
39024
- setServerKey(next.trim());
39061
+ const key = next.trim();
39062
+ if (kind === "mcp" && key !== "" && text !== void 0) {
39063
+ setServerKey(key);
39064
+ try {
39065
+ const document = readDraftServerDocument(text, key);
39066
+ if (document.keyed) setText(composeServerDocument(key, document.config, document.policy));
39067
+ } catch {}
39025
39068
  }
39026
39069
  setName(next);
39027
39070
  };
@@ -39033,6 +39076,7 @@ function ServerConfigDetail({ kind, id, create = false, t, credentials, onSaved,
39033
39076
  text,
39034
39077
  t,
39035
39078
  serverKey,
39079
+ create,
39036
39080
  disabled: busy || !editable,
39037
39081
  onValidityChange: setValid,
39038
39082
  ...credentials === void 0 ? {} : { credentials },
@@ -39043,7 +39087,7 @@ function ServerConfigDetail({ kind, id, create = false, t, credentials, onSaved,
39043
39087
  placeholder: t(nameCopy.placeholder),
39044
39088
  "aria-label": t(nameCopy.label),
39045
39089
  readOnly: !create,
39046
- disabled: busy || !nameEditable,
39090
+ disabled: busy,
39047
39091
  onChange: (event) => {
39048
39092
  if (create) changeName(event.target.value);
39049
39093
  }
@@ -39054,7 +39098,7 @@ function ServerConfigDetail({ kind, id, create = false, t, credentials, onSaved,
39054
39098
  onPolicyDraftChange: updatePolicy,
39055
39099
  ...backend === void 0 ? {} : { backend },
39056
39100
  ...fieldErrors === void 0 ? {} : { fieldErrors },
39057
- onChange: setText
39101
+ onChange: changeText
39058
39102
  }), loading || error && text === void 0 ? null : editable ? (0, react.createElement)(DetailFooterAction, { children: (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
39059
39103
  variant: "primary",
39060
39104
  disabled: busy || !saveable,
@@ -39140,6 +39184,7 @@ function McpStatusPanel({ t, credentials }) {
39140
39184
  const [view, setView] = useWorkspaceView();
39141
39185
  const [selected, setSelected] = (0, react.useState)(void 0);
39142
39186
  const [editing, setEditing] = (0, react.useState)();
39187
+ const [confirmDelete, setConfirmDelete] = (0, react.useState)();
39143
39188
  const [adding, setAdding] = (0, react.useState)(false);
39144
39189
  const [showOriginal, setShowOriginal] = (0, react.useState)(false);
39145
39190
  const flipOriginal = () => setShowOriginal((current) => !current);
@@ -39198,6 +39243,35 @@ function McpStatusPanel({ t, credentials }) {
39198
39243
  });
39199
39244
  };
39200
39245
  useTranslationRefresh(t, refresh);
39246
+ /**
39247
+ * Delete one user-owned server. A suite-declared server belongs to its
39248
+ * source, so the backend refuses it and the card offers no such action; the
39249
+ * row disappears from the re-read list because its declaration is gone.
39250
+ * The failure is reported in the panel rather than thrown, so the shared
39251
+ * confirmation closes the same way whether or not the request succeeded.
39252
+ */
39253
+ const remove = async (entry) => {
39254
+ const suiteId = entry.suiteId;
39255
+ const serverKey = entry.serverKey;
39256
+ if (suiteId === void 0 || serverKey === void 0) return;
39257
+ setError(void 0);
39258
+ try {
39259
+ await removeMcpServer(suiteId, serverKey);
39260
+ setPayload(await fetchMcpStatus());
39261
+ setSelected((current) => current?.id === entry.id ? void 0 : current);
39262
+ } catch (caught) {
39263
+ setError(clientErrorMessage(t, caught));
39264
+ }
39265
+ };
39266
+ /** Deleting is irreversible, so the card asks through the shared confirmation. */
39267
+ const askDelete = (entry) => {
39268
+ const name = mcpDisplayName(entry);
39269
+ setConfirmDelete({
39270
+ title: t("mcpDeleteTitle", { name }),
39271
+ description: t("mcpDeleteDesc", { name }),
39272
+ onConfirm: () => remove(entry)
39273
+ });
39274
+ };
39201
39275
  const toggle = (entry) => {
39202
39276
  const suiteId = entry.suiteId;
39203
39277
  const serverKey = entry.serverKey;
@@ -39278,7 +39352,8 @@ function McpStatusPanel({ t, credentials }) {
39278
39352
  t,
39279
39353
  onClick: () => setSelected(entry),
39280
39354
  onToggle: () => toggle(entry),
39281
- onEdit: () => setEditing(entry)
39355
+ onEdit: () => setEditing(entry),
39356
+ ...deletable(entry) ? { onDelete: () => askDelete(entry) } : {}
39282
39357
  }))), adding ? (0, react.createElement)(McpAddModal, {
39283
39358
  t,
39284
39359
  credentials,
@@ -39305,9 +39380,22 @@ function McpStatusPanel({ t, credentials }) {
39305
39380
  credentials,
39306
39381
  onClose: () => setEditing(void 0),
39307
39382
  onSaved: refresh
39383
+ }), (0, react.createElement)(ConfirmModal, {
39384
+ state: confirmDelete,
39385
+ confirmLabel: t("mcpDeleteConfirm"),
39386
+ cancelLabel: t("cancel"),
39387
+ onClose: () => setConfirmDelete(void 0)
39308
39388
  }));
39309
39389
  }
39310
39390
  /**
39391
+ * Whether this row is the user's own declaration. A suite declares its servers
39392
+ * in its own `mcp.json`, and an observed mount has no declaration here at all,
39393
+ * so only a user-owned direct server is this panel's to delete.
39394
+ */
39395
+ function deletable(entry) {
39396
+ return entry.kind === "direct" && entry.suiteId !== void 0 && entry.serverKey !== void 0;
39397
+ }
39398
+ /**
39311
39399
  * One inventory card: the server name with its state tag on the identity row
39312
39400
  * and the enable switch on its trailing edge; the endpoint on the body row;
39313
39401
  * tool count and transport on the source row. The reason text, the state
@@ -39315,7 +39403,7 @@ function McpStatusPanel({ t, credentials }) {
39315
39403
  * configuration) live in the detail dialog, so a wall of failing cards stays
39316
39404
  * scannable.
39317
39405
  */
39318
- function McpCard({ entry, t, onClick, onToggle, onEdit }) {
39406
+ function McpCard({ entry, t, onClick, onToggle, onEdit, onDelete }) {
39319
39407
  const interactive = interactiveCardProps(onClick);
39320
39408
  const toolCount = entry.tools.length === 1 ? t("mcpTool") : t("mcpTools");
39321
39409
  const switchable = entry.suiteId !== void 0 && entry.serverKey !== void 0 && entry.state !== "foreign";
@@ -39340,7 +39428,16 @@ function McpCard({ entry, t, onClick, onToggle, onEdit }) {
39340
39428
  event.stopPropagation();
39341
39429
  onEdit();
39342
39430
  }
39343
- }, (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutlineMedium)) : null, (0, react.createElement)("span", {
39431
+ }, (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutlineMedium)) : null, onDelete === void 0 ? null : (0, react.createElement)("button", {
39432
+ type: "button",
39433
+ className: `${resource_card_module_default.iconBtn} ${resource_card_module_default.iconBtnDanger}`,
39434
+ title: t("mcpDelete"),
39435
+ "aria-label": `${t("mcpDelete")} ${displayName}`,
39436
+ onClick: (event) => {
39437
+ event.stopPropagation();
39438
+ onDelete();
39439
+ }
39440
+ }, (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutlineMedium)), (0, react.createElement)("span", {
39344
39441
  className: resource_card_module_default.switchWrap,
39345
39442
  onClick: (event) => event.stopPropagation()
39346
39443
  }, (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
@@ -41068,10 +41165,9 @@ function settingsRegionField(field) {
41068
41165
  * renderer face for the card.
41069
41166
  * @param scope - the host configuration form for the market namespace.
41070
41167
  * @param probeSource - reads the live host-client and region state from the market API.
41071
- * @param language - the interface language the translation row's derived default follows; absent keeps the declared default.
41072
41168
  * @returns the model, the injected face, and the disposer releasing the form.
41073
41169
  */
41074
- function bindMarketCardForm(scope, probeSource, language) {
41170
+ function bindMarketCardForm(scope, probeSource) {
41075
41171
  const model = new _deepseek_ai_dsh_client_ui_primitives.SettingsFormModel(scope, [
41076
41172
  settingsBooleanField("mcpEnhanced"),
41077
41173
  settingsBooleanField("scanProjectLayouts"),
@@ -41103,16 +41199,18 @@ function bindMarketCardForm(scope, probeSource, language) {
41103
41199
  let probeGeneration = 0;
41104
41200
  const hostClientMissing = () => probe !== void 0 && !probe.hostClient.available;
41105
41201
  const compatBlocked = () => model.field("mcpEnhanced").text !== "true" && (probeStatus === "loading" || hostClientMissing());
41106
- /** Resolve the stored or staged display preference; language only supplies its default. */
41202
+ /**
41203
+ * Resolve the stored or staged display preference through the contract, so a
41204
+ * field the document says nothing about reads the plugin default instead of
41205
+ * an empty draft. A staged edit answers for itself from the moment it lands.
41206
+ */
41107
41207
  const translationField = () => {
41108
41208
  const field = model.field("translationEnabled");
41109
41209
  if (field.invalid) return field;
41110
41210
  const section = field.text === "" ? scope.getSnapshot().value : { translationEnabled: field.text === "true" };
41111
- const current = language?.current();
41112
- const enabled = effectiveTranslationEnabled(section, current);
41113
41211
  return {
41114
41212
  ...field,
41115
- text: String(enabled)
41213
+ text: String(effectiveTranslationEnabled(section))
41116
41214
  };
41117
41215
  };
41118
41216
  const project = () => {
@@ -41132,9 +41230,6 @@ function bindMarketCardForm(scope, probeSource, language) {
41132
41230
  };
41133
41231
  };
41134
41232
  const store = model.bind(project);
41135
- const unsubscribeLanguage = language?.subscribe(() => {
41136
- store.set(project());
41137
- });
41138
41233
  /** Read the host-client probe once; a second call while one is in flight is a no-op. */
41139
41234
  const loadProbe = async () => {
41140
41235
  if (probeStatus === "loading") return;
@@ -41162,7 +41257,6 @@ function bindMarketCardForm(scope, probeSource, language) {
41162
41257
  },
41163
41258
  dispose: () => {
41164
41259
  probeGeneration += 1;
41165
- unsubscribeLanguage?.();
41166
41260
  model.dispose();
41167
41261
  }
41168
41262
  };
@@ -41215,9 +41309,9 @@ function OverrideBadge(props) {
41215
41309
  onClick: props.onReset
41216
41310
  }, props.t("settingReset")));
41217
41311
  }
41218
- /** The head of one field row: its label, a standing marker, and the override state. */
41312
+ /** The head of one field row: its label and the override state. */
41219
41313
  function FieldHead(props) {
41220
- return (0, react.createElement)("div", { className: market_module_default.pluginFieldHead }, (0, react.createElement)("div", { className: market_module_default.pluginCardRowLabel }, props.label), props.tag === void 0 ? null : (0, react.createElement)("span", { className: market_module_default.pluginFieldBadges }, props.tag), props.overridden ? props.badge : null);
41314
+ return (0, react.createElement)("div", { className: market_module_default.pluginFieldHead }, (0, react.createElement)("div", { className: market_module_default.pluginCardRowLabel }, props.label), props.overridden ? props.badge : null);
41221
41315
  }
41222
41316
  /**
41223
41317
  * The reset control: the host's text button, named for what it clears. It
@@ -41258,7 +41352,6 @@ function SwitchRow(props) {
41258
41352
  return (0, react.createElement)("div", { className: market_module_default.pluginCardRow }, (0, react.createElement)("div", { className: market_module_default.pluginCardText }, (0, react.createElement)(FieldHead, {
41259
41353
  label,
41260
41354
  overridden: props.state.overridden,
41261
- ...props.field === "translationEnabled" ? { tag: (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Tag, { tone: "neutral" }, props.t("translationToggleExperimental")) } : props.field === "agentPresetsEnabled" ? { tag: (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Tag, { tone: "neutral" }, props.t("agentPresetsToggleExperimental")) } : {},
41262
41355
  badge: (0, react.createElement)(OverrideBadge, {
41263
41356
  t: props.t,
41264
41357
  disabled: props.disabled,
@@ -41354,8 +41447,8 @@ function McpPluginCard(props) {
41354
41447
  /**
41355
41448
  * Read-only agent-preset-manager preference shared by the composer slot. The
41356
41449
  * host form owns persistence; this projection carries the resolved boolean —
41357
- * the experimental capability is hidden until the user turns it on, and a
41358
- * flip shows the entry without a reload.
41450
+ * the entry shows by default and a stored false hides it, and a flip shows or
41451
+ * hides the entry without a reload.
41359
41452
  * @module client/ui/agent-presets-enabled
41360
41453
  */
41361
41454
  const state = (0, _deepseek_ai_dsh_client_store.createSnapshotStore)(MARKET_SETTINGS_DEFAULTS.agentPresetsEnabled);
@@ -41368,7 +41461,7 @@ const agentPresetsEnabled = {
41368
41461
  };
41369
41462
  /**
41370
41463
  * Follow the host form without adding persistence or polling. A form that has
41371
- * not answered yet keeps the pre-answer default (off) rather than resolving
41464
+ * not answered yet keeps the pre-answer default (on) rather than resolving
41372
41465
  * the absent section: only a defined value is resolved through the contract.
41373
41466
  * @param next - the host form for this plugin's settings namespace.
41374
41467
  * @returns an idempotent disposer; an older disposer cannot clear a newer binding.
@@ -42998,8 +43091,8 @@ const REQUIRED_PRIMITIVES = [
42998
43091
  /** Published composer seat after Permissions; blank and ongoing sessions share one icon entry. */
42999
43092
  const COMPOSER_TOGGLE_SLOT = "conversation.input.left";
43000
43093
  /**
43001
- * The slot's payload, behind the experimental switch: nothing renders while
43002
- * the setting is off, and a flip shows the entry without a reload.
43094
+ * The slot's payload, behind the preset switch: nothing renders while the
43095
+ * setting is off, and a flip shows the entry without a reload.
43003
43096
  */
43004
43097
  function PresetEntrySlot(props) {
43005
43098
  if (!useAgentPresetsEnabled() || !props.sessionId) return null;
@@ -43131,12 +43224,11 @@ function apply(ctx) {
43131
43224
  if (service === void 0) return;
43132
43225
  let card;
43133
43226
  const form = service.get(NS);
43134
- const language = bindInterfaceLanguage(service.get(LOCALE_SETTINGS_ENTRY));
43135
- const unbindTranslation = bindTranslationEnabled(form, language);
43227
+ const unbindTranslation = bindTranslationEnabled(form);
43136
43228
  const unbindPresets = bindAgentPresetsEnabled(form);
43137
43229
  const stopServed = service.whileServed([NS], () => {
43138
43230
  if (slots === void 0) return () => {};
43139
- const bound = bindMarketCardForm(form, fetchMcpBackend, language);
43231
+ const bound = bindMarketCardForm(form, fetchMcpBackend);
43140
43232
  card = bound.face;
43141
43233
  const dispose = slots.register({
43142
43234
  name: "plugins.bundle.config",