dsh-windows-mcp 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -31,6 +31,15 @@ window.__ModuleLoader__.load({
31
31
  var PREPARE_PATH = "/dsh-windows-mcp/prepare";
32
32
  var PREPARE_CANCEL_PATH = "/dsh-windows-mcp/prepare/cancel";
33
33
  var BRIDGE_PATH = "/dsh-windows-mcp/bridge";
34
+ var UPDATE_PATH = "/dsh-windows-mcp/update";
35
+ var SETTINGS_PATH = "/dsh-windows-mcp/settings";
36
+ var SETTINGS_SLOT = "settings.section";
37
+ var SETTINGS_ENTRY_ID = "windows-mcp-settings";
38
+ var SETTINGS_ORDER = 62;
39
+ var SETTINGS_NAV_ICON_MARKER = "data-dwmc-nav-icon";
40
+ var SETTINGS_NAV_ICON_STYLE_ID = PACKAGE_ID + "-settings-nav-icon-styles";
41
+ var SETTINGS_NAV_ROW_SELECTOR = '[role="dialog"] nav button';
42
+ var REPO_URL = "https://github.com/helloHub1212/dsh-windows-mcp";
34
43
  var STYLE_ID = PACKAGE_ID + "-computer-use-styles";
35
44
  var LABEL = "Computer Use";
36
45
  /** Only `slots` is injected: it is certainly provided, and a declaration for
@@ -90,6 +99,35 @@ window.__ModuleLoader__.load({
90
99
  "color:var(--dsw-alias-label-secondary);white-space:pre-wrap;word-break:break-all}",
91
100
  ".dwmc-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px}",
92
101
  "@media (prefers-reduced-motion:reduce){.dwmc-progress>i{animation:none}}",
102
+ /* Settings page: plain cards, one control per row, theme tokens only. */
103
+ ".dwmc-set{display:flex;flex-direction:column;gap:18px;max-width:720px;",
104
+ "color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.55}",
105
+ ".dwmc-set__head{display:flex;flex-direction:column;gap:4px}",
106
+ ".dwmc-set__title{margin:0;font-size:16px;font-weight:600}",
107
+ ".dwmc-set__sub{margin:0;font-size:12px;color:var(--dsw-alias-label-secondary)}",
108
+ ".dwmc-card{border:1px solid var(--dsw-alias-border-l1);border-radius:12px;",
109
+ "background:var(--dsw-alias-bg-layer-1);padding:14px 16px;display:flex;flex-direction:column;gap:12px}",
110
+ ".dwmc-card__title{margin:0;font-size:13px;font-weight:600}",
111
+ ".dwmc-field{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap}",
112
+ ".dwmc-field__label{display:flex;flex-direction:column;gap:2px;min-width:180px}",
113
+ ".dwmc-field__hint{font-size:12px;color:var(--dsw-alias-label-secondary)}",
114
+ ".dwmc-input,.dwmc-select{font:inherit;font-size:12px;color:var(--dsw-alias-label-primary);",
115
+ "background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l1);",
116
+ "border-radius:8px;padding:5px 8px;min-width:120px}",
117
+ ".dwmc-input:focus-visible,.dwmc-select:focus-visible{outline:2px solid",
118
+ "color-mix(in srgb,var(--dsw-alias-brand-primary) 45%,transparent);outline-offset:1px}",
119
+ ".dwmc-checks{display:flex;flex-wrap:wrap;gap:6px 14px}",
120
+ ".dwmc-check{display:inline-flex;align-items:center;gap:6px;font-size:12px;",
121
+ "color:var(--dsw-alias-label-primary);cursor:pointer}",
122
+ ".dwmc-check input{accent-color:var(--dsw-alias-brand-primary);margin:0}",
123
+ ".dwmc-set__actions{display:flex;align-items:center;gap:10px;flex-wrap:wrap}",
124
+ ".dwmc-star{display:inline-flex;align-items:center;gap:7px;height:30px;padding:0 14px;font:inherit;",
125
+ "font-size:12px;cursor:pointer;border-radius:8px;border:1px solid transparent;background:transparent;",
126
+ "color:var(--dsw-alias-brand-primary);",
127
+ "box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--dsw-alias-brand-primary) 42%,transparent)}",
128
+ ".dwmc-star:hover{background:color-mix(in srgb,var(--dsw-alias-brand-primary) 12%,transparent)}",
129
+ ".dwmc-star svg{width:14px;height:14px;display:block}",
130
+ ".dwmc-saved{font-size:12px;color:var(--dsw-alias-state-success-primary)}",
93
131
  ].join("");
94
132
 
95
133
  /** Inject the stylesheet once. Failures are cosmetic and never fatal. */
@@ -300,6 +338,53 @@ window.__ModuleLoader__.load({
300
338
  ready: "依赖已就绪,桌面工具可用(记得把 Computer Use 开关打开)。",
301
339
  restart: "已把桥指向新安装的版本,重启 DSH 后生效。",
302
340
  hostDown: "连不上宿主",
341
+ version: "运行版本",
342
+ latest: "最新版本",
343
+ previous: "可回退",
344
+ kept: "已保留版本",
345
+ mirror: "镜像",
346
+ updateAvailable: "有新版本",
347
+ upToDate: "已是最新",
348
+ notChecked: "还没检查",
349
+ check: "检查更新",
350
+ checking: "正在测速…",
351
+ updateTo: "更新到",
352
+ rollback: "回退到",
353
+ installVersion: "安装版本",
354
+ versionInstalled: "版本已切换,重启 DSH 生效。",
355
+ noPrevious: "没有可回退的版本",
356
+ updateHint: "更新只下载新版本、旧版本原样保留;出问题一键回退,不需要重装依赖。",
357
+ settingsTitle: "Windows-MCP 参数",
358
+ settingsNav: "桌面控制",
359
+ settingsSub: "调整桥接与更新行为;桥接参数写入配置后需要重启 DSH 才生效。",
360
+ settingsRestart: "桥接参数已写入配置,重启 DSH 后生效。",
361
+ settingsSaved: "已保存",
362
+ settingsSave: "保存",
363
+ settingsSaving: "保存中…",
364
+ settingsRevert: "还原",
365
+ settingsFailed: "保存失败",
366
+ settingsIgnored: "部分取值无效,已回退为默认值:",
367
+ sectionBridge: "桥接(重启 DSH 生效)",
368
+ sectionUpdates: "更新与镜像",
369
+ sectionAbout: "关于",
370
+ timeoutLabel: "工具调用超时",
371
+ timeoutHint: "毫秒,5000–600000。越长越不容易被中断,卡住时也等得越久。",
372
+ scaleLabel: "截图缩放",
373
+ scaleHint: "越小越省视觉 token,细节也越少。",
374
+ backendLabel: "截图后端",
375
+ backendHint: "auto 交给上游挑;dxcam 最快,pillow 最慢但最稳。",
376
+ flashLabel: "截图闪光边框",
377
+ flashHint: "关掉可少一次桌面重绘;默认关闭。",
378
+ excludeLabel: "屏蔽的工具",
379
+ excludeHint: "屏蔽的工具不会出现在模型工具表里,能省上下文。",
380
+ autoCheckLabel: "启动时检查更新",
381
+ autoCheckHint: "每次启动后台查一次最新版,结果缓存 6 小时。",
382
+ mirrorLabel: "首选镜像",
383
+ mirrorHint: "auto 用测速最快的镜像;固定某个可避开波动。",
384
+ starLabel: "点个 star",
385
+ starHint: "觉得有用就给个 star,插件会继续维护。",
386
+ aboutRepo: "仓库",
387
+ aboutVersion: "版本",
303
388
  },
304
389
  en: {
305
390
  title: "Windows desktop automation (Windows-MCP)",
@@ -327,6 +412,53 @@ window.__ModuleLoader__.load({
327
412
  ready: "Dependency ready — the desktop tools are available (turn the Computer Use switch on).",
328
413
  restart: "The bridge now points at the freshly installed build; restart DSH to apply it.",
329
414
  hostDown: "Host unreachable",
415
+ version: "Running version",
416
+ latest: "Latest version",
417
+ previous: "Rollback to",
418
+ kept: "Kept versions",
419
+ mirror: "Mirror",
420
+ updateAvailable: "update available",
421
+ upToDate: "up to date",
422
+ notChecked: "not checked yet",
423
+ check: "Check for updates",
424
+ checking: "Probing mirrors…",
425
+ updateTo: "Update to",
426
+ rollback: "Roll back to",
427
+ installVersion: "Install version",
428
+ versionInstalled: "Version switched — restart DSH to apply it.",
429
+ noPrevious: "no previous version to roll back to",
430
+ updateHint: "An update only downloads the new build; the old one is kept as-is. Roll back with one click — no reinstall.",
431
+ settingsTitle: "Windows-MCP settings",
432
+ settingsNav: "Desktop",
433
+ settingsSub: "Tune the bridge and the update behaviour. Bridge values apply after a DSH restart.",
434
+ settingsRestart: "Bridge values were written to the patch layer — restart DSH to apply them.",
435
+ settingsSaved: "Saved",
436
+ settingsSave: "Save",
437
+ settingsSaving: "Saving…",
438
+ settingsRevert: "Revert",
439
+ settingsFailed: "Could not save",
440
+ settingsIgnored: "Some values were invalid and fell back to the defaults:",
441
+ sectionBridge: "Bridge (applies after a DSH restart)",
442
+ sectionUpdates: "Updates and mirrors",
443
+ sectionAbout: "About",
444
+ timeoutLabel: "Tool call timeout",
445
+ timeoutHint: "Milliseconds, 5000–600000. Longer waits are interrupted less often, and stall longer when something hangs.",
446
+ scaleLabel: "Screenshot scale",
447
+ scaleHint: "Smaller costs fewer vision tokens and shows less detail.",
448
+ backendLabel: "Screenshot backend",
449
+ backendHint: "auto lets the server choose; dxcam is fastest, pillow slowest but most compatible.",
450
+ flashLabel: "Screenshot flash border",
451
+ flashHint: "Turning it off saves one desktop repaint; off by default.",
452
+ excludeLabel: "Hidden tools",
453
+ excludeHint: "Hidden tools stay out of the model's tool table, which saves context.",
454
+ autoCheckLabel: "Check for updates at boot",
455
+ autoCheckHint: "One background check per boot, cached for six hours.",
456
+ mirrorLabel: "Preferred mirror",
457
+ mirrorHint: "auto uses the fastest probe; pinning one avoids jitter.",
458
+ starLabel: "Star on GitHub",
459
+ starHint: "If it helps, a star keeps the plugin maintained.",
460
+ aboutRepo: "Repository",
461
+ aboutVersion: "Version",
330
462
  },
331
463
  };
332
464
 
@@ -381,6 +513,19 @@ window.__ModuleLoader__.load({
381
513
  });
382
514
  }
383
515
 
516
+ /** POST JSON — used by the version manager (check / install / activate / rollback). */
517
+ function postJson(path, payload) {
518
+ return fetch(apiUrl(path), {
519
+ method: "POST",
520
+ headers: { accept: "application/json", "content-type": "application/json" },
521
+ body: JSON.stringify(payload || {}),
522
+ }).then(function (response) {
523
+ return response.json().then(function (body) {
524
+ return { ok: response.ok, status: response.status, body: body };
525
+ });
526
+ });
527
+ }
528
+
384
529
  function statusDot(tone) {
385
530
  return React.createElement("span", { className: "dwmc-dot", "data-tone": tone });
386
531
  }
@@ -419,6 +564,19 @@ window.__ModuleLoader__.load({
419
564
  var installed = dependency.installed === true;
420
565
  var bridged = typeof state.bridged === "number" ? state.bridged : null;
421
566
  var log = Array.isArray(prepare.log) ? prepare.log : [];
567
+ var update = state.update || {};
568
+ var versions = update.versions || {};
569
+ var check = update.check || {};
570
+ var install = update.install || {};
571
+ var updateAvailable = versions.updateAvailable === true && typeof check.latest === "string";
572
+ var mirrors = Array.isArray(check.mirrors) ? check.mirrors : [];
573
+ var bestMirror = null;
574
+ for (var mirrorIndex = 0; mirrorIndex < mirrors.length; mirrorIndex += 1) {
575
+ if (mirrors[mirrorIndex] && mirrors[mirrorIndex].id === check.best) {
576
+ bestMirror = mirrors[mirrorIndex];
577
+ break;
578
+ }
579
+ }
422
580
  var profile = dependency.profile && typeof dependency.profile === "object" ? dependency.profile : null;
423
581
  var profileTarget =
424
582
  profile === null
@@ -441,10 +599,28 @@ window.__ModuleLoader__.load({
441
599
  });
442
600
  };
443
601
 
602
+ var actUpdate = function (payload) {
603
+ setBusy(true);
604
+ postJson(UPDATE_PATH, payload)
605
+ .catch(function () {
606
+ /* the next poll reports the truth */
607
+ })
608
+ .then(function () {
609
+ setBusy(false);
610
+ reload();
611
+ });
612
+ };
613
+
614
+ var speedText = function (bytesPerSecond) {
615
+ var value = Number(bytesPerSecond);
616
+ if (!isFinite(value) || value <= 0) return "—";
617
+ return value >= 1048576 ? (value / 1048576).toFixed(1) + " MB/s" : Math.max(1, Math.round(value / 1024)) + " KB/s";
618
+ };
619
+
444
620
  var head = React.createElement(
445
621
  "div",
446
622
  { className: "dwmc-panel__head", key: "head" },
447
- statusDot(running ? "warn" : installed ? "on" : "warn"),
623
+ statusDot(running ? "warn" : updateAvailable ? "warn" : installed ? "on" : "warn"),
448
624
  React.createElement("span", null, t.title),
449
625
  );
450
626
 
@@ -452,11 +628,23 @@ window.__ModuleLoader__.load({
452
628
  if (!compact) {
453
629
  var rows = [
454
630
  [t.dependency, installed ? t.installed + (dependency.version ? " " + dependency.version : "") : dependency.uv === null ? t.uvMissing : t.missing],
455
- [t.tool, dependency.executable || "—"],
456
- [t.bridge, bridged === null ? "—" : bridged + " " + t.bridgedTools],
457
- [t.profile, profileTarget],
458
- [t.toggle, state.enabled === true ? t.on : t.off],
631
+ [t.version, versions.active ? "v" + versions.active : dependency.version ? "v" + dependency.version : "—"],
632
+ [
633
+ t.latest,
634
+ typeof check.latest === "string"
635
+ ? "v" + check.latest + " · " + (updateAvailable ? t.updateAvailable : t.upToDate)
636
+ : t.notChecked,
637
+ ],
459
638
  ];
639
+ if (typeof versions.previous === "string") rows.push([t.previous, "v" + versions.previous]);
640
+ if (Array.isArray(versions.available) && versions.available.length > 0) {
641
+ rows.push([t.kept, versions.available.map(function (version) { return "v" + version; }).join(", ")]);
642
+ }
643
+ if (bestMirror !== null) rows.push([t.mirror, bestMirror.label + " · " + speedText(bestMirror.speedBps)]);
644
+ rows.push([t.tool, dependency.executable || "—"]);
645
+ rows.push([t.bridge, bridged === null ? "—" : bridged + " " + t.bridgedTools]);
646
+ rows.push([t.profile, profileTarget]);
647
+ rows.push([t.toggle, state.enabled === true ? t.on : t.off]);
460
648
  children.push(
461
649
  React.createElement(
462
650
  "dl",
@@ -473,7 +661,13 @@ window.__ModuleLoader__.load({
473
661
 
474
662
  if (running) {
475
663
  children.push(React.createElement("div", { className: "dwmc-progress", key: "progress" }, React.createElement("i")));
476
- children.push(React.createElement("p", { className: "dwmc-note", key: "running" }, t.running));
664
+ children.push(
665
+ React.createElement(
666
+ "p",
667
+ { className: "dwmc-note", key: "running" },
668
+ (install.version ? t.installVersion + " v" + install.version + "… " : "") + t.running,
669
+ ),
670
+ );
477
671
  var tail = log.slice(-3).join("\n");
478
672
  if (tail) children.push(React.createElement("pre", { className: "dwmc-log", key: "log" }, tail));
479
673
  } else if (prepare.phase === "failed") {
@@ -492,17 +686,46 @@ window.__ModuleLoader__.load({
492
686
  } else if (!installed && !running) {
493
687
  children.push(React.createElement("p", { className: "dwmc-note", key: "estimate" }, t.estimate));
494
688
  }
689
+ if (!compact && (running || updateAvailable)) {
690
+ children.push(React.createElement("p", { className: "dwmc-note", key: "updateHint" }, t.updateHint));
691
+ }
495
692
  if (view.phase === "error") {
496
693
  children.push(React.createElement("p", { className: "dwmc-error", key: "offline" }, t.hostDown + ": " + view.error));
497
694
  }
498
695
 
499
696
  var actions = [];
500
697
  if (running) {
501
- actions.push(actionButton(t.cancel, function () { act(PREPARE_CANCEL_PATH); }, busy, true));
502
- } else if (!installed) {
503
- actions.push(actionButton(t.install, function () { act(PREPARE_PATH); }, busy));
698
+ actions.push(actionButton(t.cancel, function () { actUpdate({ action: "cancel" }); }, busy, true));
504
699
  } else {
505
- actions.push(actionButton(t.reinstall, function () { act(PREPARE_PATH); }, busy, true));
700
+ actions.push(
701
+ actionButton(
702
+ check.phase === "checking" ? t.checking : t.check,
703
+ function () { actUpdate({ action: "check" }); },
704
+ busy,
705
+ true,
706
+ ),
707
+ );
708
+ if (updateAvailable) {
709
+ actions.push(
710
+ actionButton(
711
+ t.updateTo + " v" + check.latest,
712
+ function () { actUpdate({ action: "install", version: check.latest }); },
713
+ busy,
714
+ ),
715
+ );
716
+ }
717
+ if (typeof versions.previous === "string") {
718
+ actions.push(
719
+ actionButton(
720
+ t.rollback + " v" + versions.previous,
721
+ function () { actUpdate({ action: "rollback" }); },
722
+ busy,
723
+ true,
724
+ ),
725
+ );
726
+ }
727
+ if (!installed) actions.push(actionButton(t.install, function () { act(PREPARE_PATH); }, busy));
728
+ else actions.push(actionButton(t.reinstall, function () { act(PREPARE_PATH); }, busy, true));
506
729
  if (prepare.bridge && prepare.bridge.ok !== true) {
507
730
  actions.push(actionButton(t.pointBridge, function () { act(BRIDGE_PATH); }, busy, true));
508
731
  }
@@ -517,6 +740,352 @@ window.__ModuleLoader__.load({
517
740
  return React.createElement(WindowsMcpPanel, { __compact: true });
518
741
  }
519
742
 
743
+ /* ------------------------------------------------------------------ *
744
+ * settings page
745
+ * ------------------------------------------------------------------ */
746
+
747
+ var SCALE_CHOICES = [0.25, 0.5, 0.75, 1];
748
+ var BACKEND_CHOICES = ["auto", "dxcam", "mss", "pillow"];
749
+ var MIRROR_CHOICES = ["auto", "ustc", "aliyun", "tencent", "huawei", "nju", "tuna", "pypi"];
750
+ var EXCLUDE_CHOICES = ["App", "PowerShell", "FileSystem"];
751
+
752
+ /** A mutable copy of the host settings, so a form never edits state in place. */
753
+ function cloneSettings(input) {
754
+ var bridge = input && input.bridge ? input.bridge : {};
755
+ var plugin = input && input.plugin ? input.plugin : {};
756
+ return {
757
+ bridge: {
758
+ toolCallTimeoutMs: Number(bridge.toolCallTimeoutMs),
759
+ excludeTools: Array.isArray(bridge.excludeTools) ? bridge.excludeTools.slice() : [],
760
+ screenshotScale: Number(bridge.screenshotScale),
761
+ screenshotBackend: String(bridge.screenshotBackend || "auto"),
762
+ disableFlash: bridge.disableFlash === true,
763
+ },
764
+ plugin: {
765
+ autoCheckUpdates: plugin.autoCheckUpdates === true,
766
+ preferredMirror: String(plugin.preferredMirror || "auto"),
767
+ },
768
+ };
769
+ }
770
+
771
+ /** GET/POST the host settings; only the settings page mounts this. */
772
+ function useSettings() {
773
+ var pair = React.useState({ phase: "loading", view: null, error: null });
774
+ var view = pair[0];
775
+ var setView = pair[1];
776
+ var savedPair = React.useState({ phase: "idle", body: null, error: null });
777
+ var saved = savedPair[0];
778
+ var setSaved = savedPair[1];
779
+
780
+ var load = React.useCallback(function () {
781
+ return fetch(apiUrl(SETTINGS_PATH), { headers: { accept: "application/json" } })
782
+ .then(function (response) {
783
+ return response.json();
784
+ })
785
+ .then(function (body) {
786
+ setView({ phase: "ready", view: body, error: null });
787
+ return body;
788
+ })
789
+ .catch(function (error) {
790
+ setView({ phase: "error", view: null, error: String(error && error.message ? error.message : error) });
791
+ });
792
+ }, []);
793
+
794
+ React.useEffect(
795
+ function () {
796
+ load();
797
+ },
798
+ [load],
799
+ );
800
+
801
+ var save = React.useCallback(function (next) {
802
+ setSaved({ phase: "saving", body: null, error: null });
803
+ return fetch(apiUrl(SETTINGS_PATH), {
804
+ method: "POST",
805
+ headers: { accept: "application/json", "content-type": "application/json" },
806
+ body: JSON.stringify({ settings: next }),
807
+ })
808
+ .then(function (response) {
809
+ return response.json();
810
+ })
811
+ .then(function (body) {
812
+ setView({ phase: "ready", view: body, error: null });
813
+ setSaved({ phase: body && body.ok === false ? "error" : "saved", body: body, error: null });
814
+ return body;
815
+ })
816
+ .catch(function (error) {
817
+ setSaved({ phase: "error", body: null, error: String(error && error.message ? error.message : error) });
818
+ return null;
819
+ });
820
+ }, []);
821
+
822
+ return { view: view, saved: saved, save: save, reload: load };
823
+ }
824
+
825
+ function starIcon() {
826
+ return React.createElement(
827
+ "svg",
828
+ { viewBox: "0 0 16 16", fill: "currentColor", "aria-hidden": "true" },
829
+ React.createElement("path", {
830
+ d: "M8 1.2l2.06 4.18 4.61.67-3.34 3.25.79 4.6L8 11.73l-4.12 2.17.79-4.6L1.33 6.05l4.61-.67z",
831
+ }),
832
+ );
833
+ }
834
+
835
+ /** Settings → left rail: the plugin's parameter page. */
836
+ function SettingsPage() {
837
+ var t = copy();
838
+ var host = useSettings();
839
+ var view = host.view;
840
+ var draftPair = React.useState(null);
841
+ var draft = draftPair[0];
842
+ var setDraft = draftPair[1];
843
+
844
+ var ready = view.phase === "ready" && view.view && view.view.settings ? view.view : null;
845
+
846
+ React.useEffect(
847
+ function () {
848
+ if (ready !== null) setDraft(cloneSettings(ready.settings));
849
+ },
850
+ [ready === null ? null : JSON.stringify(ready.settings)],
851
+ );
852
+
853
+ if (view.phase === "loading") return React.createElement("p", { className: "dwmc-note" }, t.checking);
854
+ if (view.phase === "error") return React.createElement("p", { className: "dwmc-error" }, t.hostDown + " — " + String(view.error || ""));
855
+ if (draft === null || ready === null) return React.createElement("p", { className: "dwmc-note" }, t.checking);
856
+
857
+ var dirty = JSON.stringify(draft) !== JSON.stringify(cloneSettings(ready.settings));
858
+ var saving = host.saved.phase === "saving";
859
+
860
+ var update = function (section, key, value) {
861
+ var next = cloneSettings(draft);
862
+ next[section][key] = value;
863
+ setDraft(next);
864
+ };
865
+ var toggleExclude = function (name) {
866
+ var next = cloneSettings(draft);
867
+ var at = next.bridge.excludeTools.indexOf(name);
868
+ if (at === -1) next.bridge.excludeTools.push(name);
869
+ else next.bridge.excludeTools.splice(at, 1);
870
+ setDraft(next);
871
+ };
872
+ var save = function () {
873
+ host.save(draft).then(function (body) {
874
+ if (body && body.settings) setDraft(cloneSettings(body.settings));
875
+ });
876
+ };
877
+
878
+ var field = function (label, hint, control) {
879
+ return React.createElement(
880
+ "div",
881
+ { className: "dwmc-field", key: label },
882
+ React.createElement(
883
+ "div",
884
+ { className: "dwmc-field__label" },
885
+ React.createElement("span", null, label),
886
+ hint ? React.createElement("span", { className: "dwmc-field__hint" }, hint) : null,
887
+ ),
888
+ control,
889
+ );
890
+ };
891
+ var check = function (label, checked, onChange, key) {
892
+ return React.createElement(
893
+ "label",
894
+ { className: "dwmc-check", key: key || label },
895
+ React.createElement("input", { type: "checkbox", checked: checked === true, onChange: function (event) { onChange(event.target.checked); } }),
896
+ label,
897
+ );
898
+ };
899
+ var card = function (title, children) {
900
+ return React.createElement(
901
+ "section",
902
+ { className: "dwmc-card" },
903
+ React.createElement("h3", { className: "dwmc-card__title" }, title),
904
+ children,
905
+ );
906
+ };
907
+
908
+ var bridgeCard = card(t.sectionBridge, [
909
+ field(
910
+ t.timeoutLabel,
911
+ t.timeoutHint,
912
+ React.createElement("input", {
913
+ className: "dwmc-input",
914
+ type: "number",
915
+ min: 5000,
916
+ max: 600000,
917
+ step: 1000,
918
+ value: String(draft.bridge.toolCallTimeoutMs),
919
+ onChange: function (event) {
920
+ update("bridge", "toolCallTimeoutMs", Number(event.target.value));
921
+ },
922
+ }),
923
+ ),
924
+ field(
925
+ t.scaleLabel,
926
+ t.scaleHint,
927
+ React.createElement(
928
+ "select",
929
+ {
930
+ className: "dwmc-select",
931
+ value: String(draft.bridge.screenshotScale),
932
+ onChange: function (event) {
933
+ update("bridge", "screenshotScale", Number(event.target.value));
934
+ },
935
+ },
936
+ SCALE_CHOICES.map(function (scale) {
937
+ return React.createElement("option", { key: String(scale), value: String(scale) }, String(scale));
938
+ }),
939
+ ),
940
+ ),
941
+ field(
942
+ t.backendLabel,
943
+ t.backendHint,
944
+ React.createElement(
945
+ "select",
946
+ {
947
+ className: "dwmc-select",
948
+ value: draft.bridge.screenshotBackend,
949
+ onChange: function (event) {
950
+ update("bridge", "screenshotBackend", event.target.value);
951
+ },
952
+ },
953
+ BACKEND_CHOICES.map(function (backend) {
954
+ return React.createElement("option", { key: backend, value: backend }, backend);
955
+ }),
956
+ ),
957
+ ),
958
+ field(
959
+ t.flashLabel,
960
+ t.flashHint,
961
+ React.createElement("input", {
962
+ type: "checkbox",
963
+ checked: draft.bridge.disableFlash === false ? false : true,
964
+ onChange: function (event) {
965
+ update("bridge", "disableFlash", event.target.checked);
966
+ },
967
+ }),
968
+ ),
969
+ field(
970
+ t.excludeLabel,
971
+ t.excludeHint,
972
+ React.createElement(
973
+ "div",
974
+ { className: "dwmc-checks" },
975
+ EXCLUDE_CHOICES.map(function (name) {
976
+ return check(name, draft.bridge.excludeTools.indexOf(name) !== -1, function () { toggleExclude(name); }, name);
977
+ }),
978
+ ),
979
+ ),
980
+ ]);
981
+
982
+ var updatesCard = card(t.sectionUpdates, [
983
+ field(
984
+ t.autoCheckLabel,
985
+ t.autoCheckHint,
986
+ React.createElement("input", {
987
+ type: "checkbox",
988
+ checked: draft.plugin.autoCheckUpdates === true,
989
+ onChange: function (event) {
990
+ update("plugin", "autoCheckUpdates", event.target.checked);
991
+ },
992
+ }),
993
+ ),
994
+ field(
995
+ t.mirrorLabel,
996
+ t.mirrorHint,
997
+ React.createElement(
998
+ "select",
999
+ {
1000
+ className: "dwmc-select",
1001
+ value: draft.plugin.preferredMirror,
1002
+ onChange: function (event) {
1003
+ update("plugin", "preferredMirror", event.target.value);
1004
+ },
1005
+ },
1006
+ MIRROR_CHOICES.map(function (mirror) {
1007
+ return React.createElement("option", { key: mirror, value: mirror }, mirror);
1008
+ }),
1009
+ ),
1010
+ ),
1011
+ ]);
1012
+
1013
+ var notes = [];
1014
+ if (host.saved.phase === "saved") notes.push(React.createElement("span", { key: "saved", className: "dwmc-saved" }, t.settingsSaved));
1015
+ if (host.saved.phase === "error") notes.push(React.createElement("span", { key: "err", className: "dwmc-error" }, t.settingsFailed));
1016
+ if (ready.restartRequired && host.saved.phase !== "saving") {
1017
+ notes.push(React.createElement("span", { key: "restart", className: "dwmc-warn" }, t.settingsRestart));
1018
+ }
1019
+ if (host.saved.body && Array.isArray(host.saved.body.problems) && host.saved.body.problems.length > 0) {
1020
+ notes.push(React.createElement("span", { key: "ignored", className: "dwmc-warn" }, t.settingsIgnored + " " + host.saved.body.problems.join(", ")));
1021
+ }
1022
+
1023
+ var aboutCard = card(t.sectionAbout, [
1024
+ React.createElement(
1025
+ "div",
1026
+ { className: "dwmc-rows" },
1027
+ React.createElement("dt", null, t.aboutVersion),
1028
+ React.createElement("dd", { className: "dwmc-mono" }, "v" + String(ready.pluginVersion || "?")),
1029
+ React.createElement("dt", null, t.aboutRepo),
1030
+ React.createElement(
1031
+ "dd",
1032
+ null,
1033
+ React.createElement("a", { className: "dwmc-mono", href: REPO_URL, target: "_blank", rel: "noopener noreferrer" }, REPO_URL.replace("https://", "")),
1034
+ ),
1035
+ ),
1036
+ React.createElement(
1037
+ "div",
1038
+ { className: "dwmc-set__actions" },
1039
+ React.createElement(
1040
+ "button",
1041
+ {
1042
+ type: "button",
1043
+ className: "dwmc-star",
1044
+ onClick: function () {
1045
+ try {
1046
+ window.open(REPO_URL, "_blank", "noopener,noreferrer");
1047
+ } catch (error) {
1048
+ /* a blocked popup is not worth failing the page over */
1049
+ }
1050
+ },
1051
+ },
1052
+ starIcon(),
1053
+ t.starLabel,
1054
+ ),
1055
+ React.createElement("span", { className: "dwmc-note" }, t.starHint),
1056
+ ),
1057
+ ]);
1058
+
1059
+ return React.createElement(
1060
+ "div",
1061
+ { className: "dwmc-set" },
1062
+ React.createElement(
1063
+ "div",
1064
+ { className: "dwmc-set__head" },
1065
+ React.createElement("h2", { className: "dwmc-set__title" }, t.settingsTitle),
1066
+ React.createElement("p", { className: "dwmc-set__sub" }, t.settingsSub),
1067
+ ),
1068
+ bridgeCard,
1069
+ updatesCard,
1070
+ aboutCard,
1071
+ React.createElement(
1072
+ "div",
1073
+ { className: "dwmc-set__actions" },
1074
+ React.createElement(
1075
+ "button",
1076
+ { type: "button", className: "dwmc-btn", onClick: save, disabled: saving || !dirty },
1077
+ saving ? t.settingsSaving : t.settingsSave,
1078
+ ),
1079
+ React.createElement(
1080
+ "button",
1081
+ { type: "button", className: "dwmc-btn dwmc-btn--quiet", onClick: function () { setDraft(cloneSettings(ready.settings)); }, disabled: saving || !dirty },
1082
+ t.settingsRevert,
1083
+ ),
1084
+ notes.length > 0 ? React.createElement("span", { className: "dwmc-set__actions" }, notes) : null,
1085
+ ),
1086
+ );
1087
+ }
1088
+
520
1089
  /* ------------------------------------------------------------------ *
521
1090
  * registration
522
1091
  * ------------------------------------------------------------------ */
@@ -590,6 +1159,135 @@ window.__ModuleLoader__.load({
590
1159
  }
591
1160
  }
592
1161
 
1162
+ /**
1163
+ * Settings → left rail. `settings.section` is a list slot: one page per
1164
+ * registered entry, ordered by `order`, labelled by `label()`.
1165
+ */
1166
+ /**
1167
+ * The glyph of the settings entry: a monitor with a cursor cut out of its
1168
+ * screen ("this page drives the desktop"). `settings.section` has no icon
1169
+ * field, so the entry is painted on the shell's nav row: the row is marked
1170
+ * by matching its visible text against our own localized label, the shell's
1171
+ * gear is hidden and a mask-image painted in `currentColor` takes its place.
1172
+ */
1173
+ function settingsNavMaskSvg() {
1174
+ return [
1175
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="#000">',
1176
+ '<path fill-rule="evenodd" d="M2.6 2.6h10.8c.66 0 1.2.54 1.2 1.2v6.4c0 .66-.54 1.2-1.2 1.2H2.6',
1177
+ 'c-.66 0-1.2-.54-1.2-1.2V3.8c0-.66.54-1.2 1.2-1.2Zm3.1 2.5 4.5 2.85-2.1.36.98 2.06-1.16.55',
1178
+ '-0.97-2.05-1.3 1.05z"/>',
1179
+ '<rect x="7" y="11.2" width="2" height="1.7" rx=".3"/>',
1180
+ '<rect x="5.2" y="12.9" width="5.6" height="1.4" rx=".7"/>',
1181
+ "</svg>",
1182
+ ].join("");
1183
+ }
1184
+
1185
+ /** Encoded as a CSS `mask-image` source; the mask reads alpha only. */
1186
+ function settingsNavMaskUrl(svg) {
1187
+ var source = typeof svg === "string" ? svg : settingsNavMaskSvg();
1188
+ return "data:image/svg+xml," + encodeURIComponent(source).replace(/'/gu, "%27");
1189
+ }
1190
+
1191
+ /**
1192
+ * Whether a nav row is this plugin's own. Pure: the row whose visible text
1193
+ * is the label the shell currently projects, trimmed. An empty label matches
1194
+ * nothing, so a locale that has not resolved yet never marks the whole nav.
1195
+ */
1196
+ function isOwnSettingsNavRow(rowText, wantedLabel) {
1197
+ var wanted = String(wantedLabel === null || wantedLabel === undefined ? "" : wantedLabel).trim();
1198
+ if (wanted.length === 0) return false;
1199
+ return String(rowText === null || rowText === undefined ? "" : rowText).trim() === wanted;
1200
+ }
1201
+
1202
+ /** Stylesheet for the marked row: hide the shell icon, draw ours. */
1203
+ function settingsNavIconCss(maskUrl) {
1204
+ var marker = "[" + SETTINGS_NAV_ICON_MARKER + "]";
1205
+ return [
1206
+ marker + " > svg{display:none}",
1207
+ marker + "::before{content:'';flex:none;width:16px;height:16px;",
1208
+ "background-color:currentColor;",
1209
+ "-webkit-mask-image:url('" + maskUrl + "');mask-image:url('" + maskUrl + "');",
1210
+ "-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;",
1211
+ "-webkit-mask-position:center;mask-position:center;",
1212
+ "-webkit-mask-size:16px 16px;mask-size:16px 16px}",
1213
+ ].join("");
1214
+ }
1215
+
1216
+ /**
1217
+ * Install the nav glyph. Idempotent, and a no-op outside a browser. The
1218
+ * observer follows re-renders and locale switches, so the text and the
1219
+ * glyph can never disagree; the marker and the stylesheet belong to the
1220
+ * fiber when the client context offers `effect`.
1221
+ */
1222
+ function installSettingsNavIcon(ctx) {
1223
+ if (typeof document === "undefined" || typeof MutationObserver !== "function") return null;
1224
+ var install = function () {
1225
+ var existing = document.getElementById(SETTINGS_NAV_ICON_STYLE_ID);
1226
+ if (existing !== null) return function () {};
1227
+ var style = document.createElement("style");
1228
+ style.id = SETTINGS_NAV_ICON_STYLE_ID;
1229
+ style.textContent = settingsNavIconCss(settingsNavMaskUrl());
1230
+ document.head.appendChild(style);
1231
+ var disposed = false;
1232
+ var scheduled = false;
1233
+ var sync = function () {
1234
+ scheduled = false;
1235
+ if (disposed) return;
1236
+ var wanted = copy().settingsNav;
1237
+ var rows = document.querySelectorAll(SETTINGS_NAV_ROW_SELECTOR);
1238
+ for (var index = 0; index < rows.length; index += 1) {
1239
+ var row = rows[index];
1240
+ if (isOwnSettingsNavRow(row.textContent, wanted)) row.setAttribute(SETTINGS_NAV_ICON_MARKER, "");
1241
+ else row.removeAttribute(SETTINGS_NAV_ICON_MARKER);
1242
+ }
1243
+ };
1244
+ var schedule = function () {
1245
+ if (scheduled || disposed) return;
1246
+ scheduled = true;
1247
+ if (typeof queueMicrotask === "function") queueMicrotask(sync);
1248
+ else setTimeout(sync, 0);
1249
+ };
1250
+ sync();
1251
+ var observer = new MutationObserver(schedule);
1252
+ observer.observe(document.body, { childList: true, subtree: true, characterData: true });
1253
+ return function () {
1254
+ disposed = true;
1255
+ observer.disconnect();
1256
+ var marked = document.querySelectorAll("[" + SETTINGS_NAV_ICON_MARKER + "]");
1257
+ for (var at = 0; at < marked.length; at += 1) marked[at].removeAttribute(SETTINGS_NAV_ICON_MARKER);
1258
+ if (style.parentNode !== null) style.parentNode.removeChild(style);
1259
+ };
1260
+ };
1261
+ if (ctx && typeof ctx.effect === "function") return ctx.effect(install, "dsh-windows-mcp: settings nav icon");
1262
+ return install();
1263
+ }
1264
+
1265
+ function registerSettingsSection(ctx) {
1266
+ try {
1267
+ ctx.slots.inject(SETTINGS_SLOT, function () {
1268
+ return ctx.slots.register(
1269
+ {
1270
+ name: SETTINGS_SLOT,
1271
+ id: SETTINGS_ENTRY_ID,
1272
+ order: SETTINGS_ORDER,
1273
+ label: function () {
1274
+ return copy().settingsNav;
1275
+ },
1276
+ },
1277
+ function SettingsSection() {
1278
+ return React.createElement(SettingsPage, null);
1279
+ },
1280
+ );
1281
+ });
1282
+ } catch (error) {
1283
+ try {
1284
+ console.error("[" + PACKAGE_ID + "] settings section registration failed:", error);
1285
+ } catch (ignored) {
1286
+ /* nothing to do */
1287
+ }
1288
+ }
1289
+ }
1290
+
593
1291
  /**
594
1292
  * @param ctx - client cordis context.
595
1293
  */
@@ -597,11 +1295,30 @@ window.__ModuleLoader__.load({
597
1295
  installStyles();
598
1296
  registerComposerPill(ctx);
599
1297
  registerBundleSlots(ctx);
1298
+ registerSettingsSection(ctx);
1299
+ installSettingsNavIcon(ctx);
600
1300
  }
601
1301
 
602
1302
  exports.apply = apply;
603
1303
  exports.inject = inject;
604
- exports.__internals = { CSS: CSS, apiUrl: apiUrl, tooltipFor: tooltipFor, LABEL: LABEL, STATE_PATH: STATE_PATH };
1304
+ exports.__internals = {
1305
+ CSS: CSS,
1306
+ apiUrl: apiUrl,
1307
+ tooltipFor: tooltipFor,
1308
+ LABEL: LABEL,
1309
+ STATE_PATH: STATE_PATH,
1310
+ UPDATE_PATH: UPDATE_PATH,
1311
+ SETTINGS_PATH: SETTINGS_PATH,
1312
+ REPO_URL: REPO_URL,
1313
+ SETTINGS_SLOT: SETTINGS_SLOT,
1314
+ SETTINGS_NAV_ICON: settingsNavMaskSvg(),
1315
+ SETTINGS_NAV_ICON_MARKER: SETTINGS_NAV_ICON_MARKER,
1316
+ isOwnSettingsNavRow: isOwnSettingsNavRow,
1317
+ settingsNavIconCss: settingsNavIconCss,
1318
+ settingsNavMaskUrl: settingsNavMaskUrl,
1319
+ cloneSettings: cloneSettings,
1320
+ SettingsPage: SettingsPage,
1321
+ };
605
1322
  return module.exports;
606
1323
  },
607
1324
  });