@dsh-electron/dsh-plugin-multi-root-workspace 0.1.12 → 0.1.14

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 (49) hide show
  1. package/CHANGELOG.en.md +29 -1
  2. package/CHANGELOG.md +29 -1
  3. package/README.en.md +9 -8
  4. package/README.md +9 -8
  5. package/cordis.patch.yml +4 -0
  6. package/detail/DETAIL.en.md +1 -0
  7. package/detail/DETAIL.md +1 -0
  8. package/detail/preview.svg +1 -0
  9. package/detail/preview.zh.svg +1 -0
  10. package/lib/client.js +492 -87
  11. package/lib/client.js.map +1 -1
  12. package/lib/{command-CmLekG1M.js → command-Dcz6nULo.js} +25 -3
  13. package/lib/command.js +1 -1
  14. package/lib/{compat-Bg2HFVMX.js → compat-veDqCOoa.js} +3 -1
  15. package/lib/compat.js +1 -1
  16. package/lib/{fs-CfzJq2wd.js → fs-Uws0ZpmV.js} +38 -0
  17. package/lib/fs.js +1 -1
  18. package/lib/index.js +4 -4
  19. package/lib/{native-win32-2waCRkPI.js → native-win32-CRNXr11U.js} +2 -1
  20. package/lib/{protocol-4zdwV7u4.js → protocol-CnR1KTXy.js} +62 -9
  21. package/lib/{registry-YTUOjmVu.js → registry-DR_sMuvS.js} +425 -125
  22. package/lib/{registry-lease-win32-DVTq9d4D.js → registry-lease-win32-DDO-c7ig.js} +2 -1
  23. package/lib/registry.js +2 -2
  24. package/lib/scope.js +36 -2
  25. package/lib/types/authority/protocol.d.ts +1 -1
  26. package/lib/types/authority/recovery.d.ts +58 -0
  27. package/lib/types/client/RootSwitcher.d.ts +8 -3
  28. package/lib/types/client/auto-review-settings.d.ts +18 -0
  29. package/lib/types/client/files-tree/FilesTree.d.ts +4 -1
  30. package/lib/types/client/files-tree/adapter.d.ts +63 -1
  31. package/lib/types/client/files-tree/locales.d.ts +1 -1
  32. package/lib/types/client/locales.d.ts +12 -0
  33. package/lib/types/client/open-in-app/locales.d.ts +1 -1
  34. package/lib/types/client/skills-settings.d.ts +12 -0
  35. package/lib/types/client/styles.d.ts +1 -1
  36. package/lib/types/compat/auto-review-client.d.ts +58 -0
  37. package/lib/types/compat/connection-rpc.d.ts +12 -0
  38. package/lib/types/compat/dsh-version.d.ts +1 -1
  39. package/lib/types/compat/files-tree.d.ts +36 -0
  40. package/lib/types/compat/fs-patch.d.ts +6 -0
  41. package/lib/types/compat/koffi.d.ts +2 -0
  42. package/lib/types/fs.d.ts +3 -1
  43. package/lib/types/registry.d.ts +98 -0
  44. package/lib/types/scope.d.ts +18 -0
  45. package/locale/en.json +7 -0
  46. package/locale/zh.json +7 -0
  47. package/package.json +89 -82
  48. package/src/auto-review/README.en.md +1 -1
  49. package/src/auto-review/README.md +1 -1
package/lib/client.js CHANGED
@@ -40,8 +40,8 @@ window.__ModuleLoader__.load({
40
40
  _deepseek_ai_dsh_client_ui_primitives = __toESM(_deepseek_ai_dsh_client_ui_primitives, 1);
41
41
  let react_jsx_runtime = require("react/jsx-runtime");
42
42
  let react_dom = require("react-dom");
43
- require("@deepseek-ai/cordis");
44
43
  let _deepseek_ai_dsh_client_store = require("@deepseek-ai/dsh-client-store");
44
+ require("@deepseek-ai/cordis");
45
45
  //#region src/compat/client-session.ts
46
46
  /**
47
47
  * The session the composed client is showing, when it can name one.
@@ -123,6 +123,48 @@ window.__ModuleLoader__.load({
123
123
  return typeof count === "number" && count > 0;
124
124
  }
125
125
  //#endregion
126
+ //#region src/compat/files-tree.ts
127
+ function nativeTreeRenderer(value) {
128
+ return typeof value === "function" ? value : void 0;
129
+ }
130
+ /** The factory consumes absolute, slash-separated keys and already-localized errors. */
131
+ function nativeTreeInput(state, failureLine, toggle, open) {
132
+ const root = state.root.replace(/[/\\]+$/, "");
133
+ const keyOf = (path) => path === state.root ? root : path;
134
+ const entries = [];
135
+ const directories = {};
136
+ for (const path of state.expanded) directories[keyOf(path)] = { loading: true };
137
+ for (const [path, level] of Object.entries(state.levels)) {
138
+ const key = keyOf(path);
139
+ if (level.kind === "loading") directories[key] = { loading: true };
140
+ else if (level.kind === "failed") directories[key] = { failure: failureLine(level.failure) };
141
+ else {
142
+ const status = {};
143
+ if (level.failure !== void 0) status.failure = failureLine(level.failure);
144
+ if (level.level.truncated) status.truncated = true;
145
+ directories[key] = status;
146
+ for (const entry of level.level.entries) entries.push({
147
+ path: `${path.replace(/[/\\]+$/, "")}/${entry.name}`,
148
+ name: entry.name,
149
+ meta: { kind: entry.type }
150
+ });
151
+ }
152
+ }
153
+ return {
154
+ root,
155
+ entries,
156
+ directories,
157
+ expanded: state.expanded.map(keyOf),
158
+ onToggle: (entry) => {
159
+ const parent = entry.path.slice(0, entry.path.lastIndexOf("/"));
160
+ toggle(parent === root ? state.root : parent, entry.path);
161
+ },
162
+ onOpen: (entry) => {
163
+ open(entry.path);
164
+ }
165
+ };
166
+ }
167
+ //#endregion
126
168
  //#region node_modules/.pnpm/zod@4.6.7/node_modules/zod/v4/core/util.js
127
169
  function getEnumValues(entries) {
128
170
  const numericValues = Object.values(entries).filter((v) => typeof v === "number");
@@ -5545,6 +5587,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5545
5587
  "skills.globalEnable": "启用附属根 Skills",
5546
5588
  "skills.globalHint": "控制所有附属目录的 Skill 发现与新调用;保留每个目录的独立选择。",
5547
5589
  "skills.settingsFailed": "设置保存失败,请重试。",
5590
+ "autoReview.title": "多根 Auto Review",
5591
+ "autoReview.experimental": "实验性",
5592
+ "autoReview.hint": "为附加根保留审核上下文并接管 Auto 审查;与插件管理页同名 Loader 行开关等价,默认关闭,仅适配 DSH 0.2.1-rc.2。",
5593
+ "autoReview.loadFailed": "无法读取插件状态。",
5594
+ "autoReview.restartRequired": "更改将在重启后生效。",
5595
+ "autoReview.settingsFailed": "设置保存失败,请重试。",
5548
5596
  "skills.userOnly": "仅用户调用",
5549
5597
  "panel.skills": "目录技能",
5550
5598
  "panel.skillsEnabled": "此目录技能已启用,点击停用",
@@ -5632,6 +5680,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5632
5680
  "skills.globalEnable": "Enable additional-root Skills",
5633
5681
  "skills.globalHint": "Controls discovery and new invocations for all additional folders, preserving each folder’s choice.",
5634
5682
  "skills.settingsFailed": "Could not save settings. Please try again.",
5683
+ "autoReview.title": "Multi-root Auto Review",
5684
+ "autoReview.experimental": "Experimental",
5685
+ "autoReview.hint": "Preserves multi-root context for Auto review and takes over the reviewer; equivalent to the same Loader row switch in the Plugin Manager. Off by default; adapted to DSH 0.2.1-rc.2 only.",
5686
+ "autoReview.loadFailed": "Could not read plugin state.",
5687
+ "autoReview.restartRequired": "The change takes effect after restart.",
5688
+ "autoReview.settingsFailed": "Could not save settings. Please try again.",
5635
5689
  "skills.userOnly": "User invocation only",
5636
5690
  "panel.skills": "Folder skills",
5637
5691
  "panel.skillsEnabled": "Skills enabled for this folder. Click to disable.",
@@ -5928,6 +5982,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5928
5982
  display: none;
5929
5983
  }
5930
5984
 
5985
+ /* Newer Files pages place the navigator beside the header instead of
5986
+ wrapping both in data-files-root. Target the page without hiding the
5987
+ portaled replacement or any of its rows. */
5988
+ [data-files-page]:has(.mrfw-fileLauncher) [data-files-path]:not([data-files-entry]) { order: -2; }
5989
+ [data-files-page]:has(.mrfw-rootSwitcher) [data-files-path]:not([data-files-entry]) { display: none; }
5990
+ [data-files-page]:has(.mrfw-rootSwitcher[data-root-kind="additional"]) [data-files-content],
5991
+ [data-files-page]:has(.mrfw-rootSwitcher[data-root-kind="additional"]) [data-files-reload] { display: none; }
5992
+ [data-files-page]:has(.mrfw-rootSwitcher[data-root-kind="additional"]) [data-slot="sidebar.right.tab.files.actions"] > *:has([data-open-target="directory"]:not(.mrfw-openSplit)),
5993
+ [data-files-page]:has(.mrfw-rootSwitcher[data-root-kind="additional"]) [data-open-target="directory"]:not(.mrfw-openSplit) { display: none; }
5994
+
5931
5995
  /* While the switcher names an additional root, the native open-in-app split
5932
5996
  button hides too: its slot props carry the pane's primary root, so it
5933
5997
  would always open the primary root no matter what the switcher selected.
@@ -6360,6 +6424,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6360
6424
  border-bottom: 0.5px solid var(--dsw-alias-border-l2);
6361
6425
  }
6362
6426
  .mrfw-skillsSettingsText { min-width: 0; }
6427
+ .mrfw-settingsTitleRow {
6428
+ display: flex;
6429
+ align-items: center;
6430
+ gap: 8px;
6431
+ }
6363
6432
  .mrfw-skillsSettingsTitle { font-size: 14px; line-height: 20px; }
6364
6433
  .mrfw-skillsSettingsDescription {
6365
6434
  margin-top: 4px;
@@ -6842,90 +6911,345 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6842
6911
  });
6843
6912
  }
6844
6913
  //#endregion
6914
+ //#region src/compat/auto-review-client.ts
6915
+ /** The module specifier `cordis.patch.yml` gives the optional row. */
6916
+ const AUTO_REVIEW_MODULE = "@dsh-electron/dsh-plugin-multi-root-workspace/auto-review";
6917
+ var AutoReviewSettingsForm = class {
6918
+ snapshot = { status: "unavailable" };
6919
+ listeners = /* @__PURE__ */ new Set();
6920
+ manager;
6921
+ stopEvents;
6922
+ epoch = 0;
6923
+ refreshSeq = 0;
6924
+ getSnapshot() {
6925
+ return this.snapshot;
6926
+ }
6927
+ subscribe(listener) {
6928
+ this.listeners.add(listener);
6929
+ return () => {
6930
+ this.listeners.delete(listener);
6931
+ };
6932
+ }
6933
+ publish(next) {
6934
+ this.snapshot = next;
6935
+ for (const listener of [...this.listeners]) listener();
6936
+ }
6937
+ attach(manager, remote) {
6938
+ this.detach();
6939
+ this.manager = manager;
6940
+ this.epoch++;
6941
+ this.publish({ status: "loading" });
6942
+ if (remote !== void 0) try {
6943
+ this.stopEvents = remote.$on("plugin-manager/changed", () => {
6944
+ this.refresh();
6945
+ });
6946
+ } catch {
6947
+ this.stopEvents = void 0;
6948
+ }
6949
+ this.refresh();
6950
+ }
6951
+ detach() {
6952
+ this.stopEvents?.();
6953
+ this.stopEvents = void 0;
6954
+ this.manager = void 0;
6955
+ this.epoch++;
6956
+ this.publish({ status: "unavailable" });
6957
+ }
6958
+ async refresh() {
6959
+ const manager = this.manager;
6960
+ const epoch = this.epoch;
6961
+ const seq = ++this.refreshSeq;
6962
+ if (manager === void 0) return;
6963
+ const result = await manager.listPlugins();
6964
+ if (manager !== this.manager || epoch !== this.epoch || seq !== this.refreshSeq) return;
6965
+ if (!result.ok) {
6966
+ this.publish({ status: "error" });
6967
+ return;
6968
+ }
6969
+ const entry = result.value.find((item) => item.moduleName === AUTO_REVIEW_MODULE);
6970
+ this.publish(entry === void 0 ? { status: "ready" } : {
6971
+ status: "ready",
6972
+ row: {
6973
+ entryId: entry.entryId,
6974
+ enabled: entry.enabled,
6975
+ writable: entry.readOnlyReason === void 0,
6976
+ ...entry.diagnostic === void 0 ? {} : { diagnostic: entry.diagnostic }
6977
+ }
6978
+ });
6979
+ }
6980
+ async setEnabled(enabled) {
6981
+ const manager = this.manager;
6982
+ const row = this.snapshot.status === "ready" ? this.snapshot.row : void 0;
6983
+ if (manager === void 0 || row === void 0) return "failed";
6984
+ const result = await manager.setPluginEnabled(row.entryId, enabled);
6985
+ await this.refresh();
6986
+ if (!result.ok) return "failed";
6987
+ const application = result.value.application;
6988
+ return application === "applied" || application === "restart-required" ? application : "failed";
6989
+ }
6990
+ };
6991
+ /**
6992
+ * Create the card's Auto Review handle and attach it when the host's
6993
+ * `pluginManager` namespace arrives. The returned form exists immediately with
6994
+ * status `unavailable` and publishes on every attach/detach/read, so callers
6995
+ * only need `subscribe` — no separate availability callback.
6996
+ * @param ctx - the client root context.
6997
+ * @returns the stable form the settings row renders.
6998
+ */
6999
+ function installAutoReviewSettings(ctx) {
7000
+ const form = new AutoReviewSettingsForm();
7001
+ clientInject(ctx, ["remote.pluginManager"], (scope) => {
7002
+ const manager = scope.get("remote.pluginManager");
7003
+ if (manager === void 0) return;
7004
+ const remote = scope.get("remote");
7005
+ form.attach(manager, remote);
7006
+ return () => {
7007
+ form.detach();
7008
+ };
7009
+ });
7010
+ return form;
7011
+ }
7012
+ //#endregion
7013
+ //#region src/client/auto-review-settings.tsx
7014
+ /**
7015
+ * Full-opacity hold for the card's message toasts — the same window upstream
7016
+ * gives a refusal banner, since these strings name what broke rather than
7017
+ * reporting a one-line limit.
7018
+ */
7019
+ const TOAST_HOLD_MS = 8e3;
7020
+ /**
7021
+ * The optional Auto Review row of the plugin settings card.
7022
+ *
7023
+ * Its switch is the Loader row's own enablement — the same write the Plugin
7024
+ * Manager's row switch performs — so the card can never disagree with the row
7025
+ * list about whether the companion is on. Feedback is a transient `Toast`
7026
+ * banner, never inline text: state-driven messages (a failed read, an entry
7027
+ * diagnostic) fire on entry and clear when their source resolves, while write
7028
+ * outcomes report once and fade. The row stays hidden entirely on hosts
7029
+ * without the `pluginManager` namespace or when the composition does not list
7030
+ * the optional entry.
7031
+ */
7032
+ function AutoReviewSettingsCard({ form, t }) {
7033
+ const snapshot = (0, react.useSyncExternalStore)((listener) => form.subscribe(listener), () => form.getSnapshot());
7034
+ const [busy, setBusy] = (0, react.useState)(false);
7035
+ const mounted = (0, react.useRef)(true);
7036
+ const toastSeq = (0, react.useRef)(0);
7037
+ const [toast, setToast] = (0, react.useState)(null);
7038
+ (0, react.useEffect)(() => {
7039
+ mounted.current = true;
7040
+ return () => {
7041
+ mounted.current = false;
7042
+ };
7043
+ }, []);
7044
+ const showToast = (0, react.useCallback)((kind, text) => {
7045
+ toastSeq.current += 1;
7046
+ setToast({
7047
+ seq: toastSeq.current,
7048
+ kind,
7049
+ text
7050
+ });
7051
+ }, []);
7052
+ const dismissToast = (0, react.useCallback)((kind) => {
7053
+ setToast((current) => current?.kind === kind ? null : current);
7054
+ }, []);
7055
+ const status = snapshot.status;
7056
+ const diagnostic = status === "ready" ? snapshot.row?.diagnostic : void 0;
7057
+ (0, react.useEffect)(() => {
7058
+ if (status === "error") showToast("load", t("autoReview.loadFailed"));
7059
+ else dismissToast("load");
7060
+ }, [
7061
+ status,
7062
+ t,
7063
+ showToast,
7064
+ dismissToast
7065
+ ]);
7066
+ (0, react.useEffect)(() => {
7067
+ if (diagnostic !== void 0 && diagnostic !== "") showToast("diagnostic", diagnostic);
7068
+ else dismissToast("diagnostic");
7069
+ }, [
7070
+ diagnostic,
7071
+ showToast,
7072
+ dismissToast
7073
+ ]);
7074
+ if (status === "unavailable") return null;
7075
+ const row = status === "ready" ? snapshot.row : void 0;
7076
+ if (status === "ready" && row === void 0) return null;
7077
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7078
+ className: "mrfw-skillsSettings",
7079
+ children: [
7080
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7081
+ className: "mrfw-skillsSettingsText",
7082
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7083
+ className: "mrfw-settingsTitleRow",
7084
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7085
+ className: "mrfw-skillsSettingsTitle",
7086
+ children: t("autoReview.title")
7087
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tag, {
7088
+ tone: "info",
7089
+ children: t("autoReview.experimental")
7090
+ })]
7091
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7092
+ className: "mrfw-skillsSettingsDescription",
7093
+ children: t("autoReview.hint")
7094
+ })]
7095
+ }),
7096
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
7097
+ label: t("autoReview.title"),
7098
+ checked: row?.enabled ?? false,
7099
+ disabled: busy || row?.writable !== true,
7100
+ onChange: (enabled) => {
7101
+ setBusy(true);
7102
+ form.setEnabled(enabled).then((result) => {
7103
+ if (!mounted.current) return;
7104
+ if (result === "restart-required") showToast("write", t("autoReview.restartRequired"));
7105
+ else if (result === "failed") showToast("write", t("autoReview.settingsFailed"));
7106
+ }).catch(() => {
7107
+ if (mounted.current) showToast("write", t("autoReview.settingsFailed"));
7108
+ }).finally(() => {
7109
+ if (mounted.current) setBusy(false);
7110
+ });
7111
+ }
7112
+ }),
7113
+ toast === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
7114
+ text: toast.text,
7115
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutlineRegular, {}),
7116
+ holdMs: TOAST_HOLD_MS,
7117
+ onDone: () => {
7118
+ setToast(null);
7119
+ }
7120
+ }, toast.seq)
7121
+ ]
7122
+ });
7123
+ }
7124
+ //#endregion
6845
7125
  //#region src/client/skills-settings.tsx
6846
7126
  const BUNDLE_KEY = "@dsh-electron/dsh-plugin-multi-root-workspace";
6847
7127
  function SkillsSettingsCard({ form, t }) {
6848
7128
  const snapshot = (0, react.useSyncExternalStore)((listener) => form.subscribe(listener), () => form.getSnapshot());
6849
- const [busy, setBusy] = (0, react.useState)(false), [error, setError] = (0, react.useState)();
7129
+ const [busy, setBusy] = (0, react.useState)(false);
6850
7130
  const mounted = (0, react.useRef)(true);
7131
+ const toastSeq = (0, react.useRef)(0);
7132
+ const [toast, setToast] = (0, react.useState)(null);
6851
7133
  (0, react.useEffect)(() => {
6852
7134
  mounted.current = true;
6853
7135
  return () => {
6854
7136
  mounted.current = false;
6855
7137
  };
6856
7138
  }, []);
7139
+ const showToast = (0, react.useCallback)((text) => {
7140
+ toastSeq.current += 1;
7141
+ setToast({
7142
+ seq: toastSeq.current,
7143
+ text
7144
+ });
7145
+ }, []);
6857
7146
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6858
7147
  className: "mrfw-skillsSettings",
6859
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6860
- className: "mrfw-skillsSettingsText",
6861
- children: [
6862
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7148
+ children: [
7149
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7150
+ className: "mrfw-skillsSettingsText",
7151
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6863
7152
  className: "mrfw-skillsSettingsTitle",
6864
7153
  children: t("skills.globalEnable")
6865
- }),
6866
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7154
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6867
7155
  className: "mrfw-skillsSettingsDescription",
6868
7156
  children: t("skills.globalHint")
6869
- }),
6870
- error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6871
- role: "alert",
6872
- children: error
6873
- }) : null
6874
- ]
6875
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
6876
- label: t("skills.globalEnable"),
6877
- checked: snapshot.value?.enabled ?? true,
6878
- disabled: busy || snapshot.status !== "ready" || !snapshot.writable,
6879
- onChange: (enabled) => {
6880
- setBusy(true);
6881
- setError(void 0);
6882
- form.mutate([{
6883
- op: "set",
6884
- path: ["enabled"],
6885
- value: enabled
6886
- }], snapshot.revision).then((ok) => {
6887
- if (mounted.current && !ok) setError(t("skills.settingsFailed"));
6888
- }).catch(() => {
6889
- if (mounted.current) setError(t("skills.settingsFailed"));
6890
- }).finally(() => {
6891
- if (mounted.current) setBusy(false);
6892
- });
6893
- }
6894
- })]
7157
+ })]
7158
+ }),
7159
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
7160
+ label: t("skills.globalEnable"),
7161
+ checked: snapshot.value?.enabled ?? true,
7162
+ disabled: busy || snapshot.status !== "ready" || !snapshot.writable,
7163
+ onChange: (enabled) => {
7164
+ setBusy(true);
7165
+ form.mutate([{
7166
+ op: "set",
7167
+ path: ["enabled"],
7168
+ value: enabled
7169
+ }], snapshot.revision).then((ok) => {
7170
+ if (mounted.current && !ok) showToast(t("skills.settingsFailed"));
7171
+ }).catch(() => {
7172
+ if (mounted.current) showToast(t("skills.settingsFailed"));
7173
+ }).finally(() => {
7174
+ if (mounted.current) setBusy(false);
7175
+ });
7176
+ }
7177
+ }),
7178
+ toast === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
7179
+ text: toast.text,
7180
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutlineRegular, {}),
7181
+ holdMs: 8e3,
7182
+ onDone: () => {
7183
+ setToast(null);
7184
+ }
7185
+ }, toast.seq)
7186
+ ]
6895
7187
  });
6896
7188
  }
7189
+ /**
7190
+ * The bundle's one `plugins.bundle.config` cell: every plugin-level switch the
7191
+ * package offers, in display order. Each row owns its own form and write path —
7192
+ * Skills through its settings namespace, Auto Review through the Plugin
7193
+ * Manager's Loader-row enablement — so one cell never serializes their state.
7194
+ */
7195
+ function BundleSettingsCard({ form, autoReviewForm, t }) {
7196
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [form !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillsSettingsCard, {
7197
+ form,
7198
+ t
7199
+ }) : null, autoReviewForm !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AutoReviewSettingsCard, {
7200
+ form: autoReviewForm,
7201
+ t
7202
+ }) : null] });
7203
+ }
6897
7204
  function installSkillsSettingsCard(ctx, t) {
6898
- let current;
7205
+ let currentSkills;
7206
+ let currentAutoReview = false;
6899
7207
  let stop;
6900
7208
  const candidates = /* @__PURE__ */ new Map();
6901
7209
  const update = () => {
6902
- const chosen = [...candidates.entries()].sort(([a], [b]) => Number(b.get("configForms") !== void 0) - Number(a.get("configForms") !== void 0))[0];
6903
- if (chosen?.[1] === current) return;
7210
+ const skillsForm = [...candidates.entries()].sort(([a], [b]) => Number(b.get("configForms") !== void 0) - Number(a.get("configForms") !== void 0))[0]?.[1];
7211
+ const autoSnapshot = autoForm.getSnapshot();
7212
+ const autoAvailable = autoSnapshot.status === "loading" || autoSnapshot.status === "error" || autoSnapshot.status === "ready" && autoSnapshot.row !== void 0;
7213
+ if (skillsForm === void 0 && !autoAvailable) {
7214
+ if (currentSkills !== void 0 || currentAutoReview) {
7215
+ stop?.();
7216
+ stop = void 0;
7217
+ currentSkills = void 0;
7218
+ currentAutoReview = false;
7219
+ }
7220
+ return;
7221
+ }
7222
+ if (currentSkills === skillsForm && currentAutoReview === autoAvailable) return;
7223
+ const slots = ctx.slots;
7224
+ if (slots === void 0) return;
6904
7225
  stop?.();
6905
7226
  stop = void 0;
6906
- current = chosen?.[1];
6907
- if (!chosen) return;
6908
- const [scope, form] = chosen;
6909
- const slots = scope.slots;
6910
- const disposers = [slots.inject("settings.plugin.item", () => slots.register({
6911
- name: "settings.plugin.item",
6912
- key: "multi-root-skills",
7227
+ currentSkills = skillsForm;
7228
+ currentAutoReview = autoAvailable;
7229
+ const disposers = [slots.inject("plugins.bundle.config", () => slots.register({
7230
+ name: "plugins.bundle.config",
7231
+ key: BUNDLE_KEY,
6913
7232
  inject: () => ({
6914
- form,
7233
+ form: skillsForm,
7234
+ autoReviewForm: autoForm,
6915
7235
  t
6916
7236
  })
6917
- }, SkillsSettingsCard)), slots.inject("plugins.bundle.config", () => slots.register({
6918
- name: "plugins.bundle.config",
6919
- key: BUNDLE_KEY,
7237
+ }, BundleSettingsCard))];
7238
+ if (skillsForm !== void 0) disposers.push(slots.inject("settings.plugin.item", () => slots.register({
7239
+ name: "settings.plugin.item",
7240
+ key: "multi-root-skills",
6920
7241
  inject: () => ({
6921
- form,
7242
+ form: skillsForm,
6922
7243
  t
6923
7244
  })
6924
- }, SkillsSettingsCard))];
7245
+ }, SkillsSettingsCard)));
6925
7246
  stop = () => {
6926
7247
  for (const dispose of disposers) dispose();
6927
7248
  };
6928
7249
  };
7250
+ const autoForm = installAutoReviewSettings(ctx);
7251
+ const stopAutoWatch = autoForm.subscribe(update);
7252
+ update();
6929
7253
  for (const service of ["settingsScope", "configForms"]) clientInject(ctx, [service], (scope) => {
6930
7254
  const form = bindSkillsForm(scope);
6931
7255
  if (form === void 0) return;
@@ -6938,6 +7262,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6938
7262
  });
6939
7263
  ctx.effect(() => () => {
6940
7264
  stop?.();
7265
+ stopAutoWatch();
6941
7266
  candidates.clear();
6942
7267
  });
6943
7268
  }
@@ -7004,7 +7329,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7004
7329
  const IconAccessReadOnly = maybeIcon(["PermissionIconReadOnlyRegular", "IconReadOnlyRegular"]);
7005
7330
  const IconAccessInherit = maybeIcon(["IconWorkspaceTreeOutline16", "IconWorkspaceTreeOutlineRegular"]);
7006
7331
  //#endregion
7007
- //#region node_modules/.pnpm/@deepseek-ai+dsh-typert-protocol@0.2.2-alpha.2_@deepseek-ai+cordis@4.0.6-alpha.1/node_modules/@deepseek-ai/dsh-typert-protocol/lib/index.js
7332
+ //#region node_modules/.pnpm/@deepseek-ai+dsh-typert-protocol@0.2.2-rc.1_@deepseek-ai+cordis@4.1.0/node_modules/@deepseek-ai/dsh-typert-protocol/lib/index.js
7008
7333
  /** The one Remote failure class shared by owners, the Gateway, and consumers. */
7009
7334
  /**
7010
7335
  * One Remote call failure: a real Error carrying its stable code and typed
@@ -7514,7 +7839,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7514
7839
  return n;
7515
7840
  }
7516
7841
  //#endregion
7517
- //#region node_modules/.pnpm/@deepseek-ai+dsh-util-workspace-path@0.2.2-alpha.2_@deepseek-ai+cordis@4.0.6-alpha.1/node_modules/@deepseek-ai/dsh-util-workspace-path/lib/index.js
7842
+ //#region node_modules/.pnpm/@deepseek-ai+dsh-util-workspace-path@0.2.2-rc.1_@deepseek-ai+cordis@4.1.0/node_modules/@deepseek-ai/dsh-util-workspace-path/lib/index.js
7518
7843
  /**
7519
7844
  * The `dsh-resource://file/…` address grammar: how a file is named across the
7520
7845
  * Sidebar and the resource model, built and parsed without touching a
@@ -7760,7 +8085,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7760
8085
  * has opened under it — the upstream body's scroll, seed, and render logic
7761
8086
  * verbatim, with the root arriving as a prop instead of `useSessions(cwd)`.
7762
8087
  */
7763
- function FilesTree({ tabId, sessionId, root, signal, useStore, actions, start, toggle, onOpen, t }) {
8088
+ function FilesTree({ tabId, sessionId, root, signal, useStore, actions, start, toggle, onOpen, t, nativeTree }) {
7764
8089
  const state = useStore((store) => store.byTab[tabId]);
7765
8090
  const bodyRef = (0, react.useRef)(null);
7766
8091
  const scrollTopRef = (0, react.useRef)(0);
@@ -7801,19 +8126,21 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7801
8126
  },
7802
8127
  t
7803
8128
  };
8129
+ const legacy = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
8130
+ className: css$1.level,
8131
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Level, {
8132
+ path: state.root,
8133
+ tree
8134
+ })
8135
+ });
8136
+ const content = nativeTree === void 0 ? legacy : nativeTree("sidebar.files.tree", nativeTreeInput(state, (failure) => failureLine(t, failure), tree.onToggle, tree.onOpen), { fallback: legacy });
7804
8137
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7805
8138
  ref: bodyRef,
7806
8139
  className: css$1.body,
7807
8140
  onScroll: (event) => {
7808
8141
  scrollTopRef.current = event.currentTarget.scrollTop;
7809
8142
  },
7810
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
7811
- className: css$1.level,
7812
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Level, {
7813
- path: state.root,
7814
- tree
7815
- })
7816
- })
8143
+ children: content
7817
8144
  });
7818
8145
  }
7819
8146
  //#endregion
@@ -7902,12 +8229,59 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7902
8229
  const store = createFilesStore();
7903
8230
  const remote = lazyFilesRemote(ctx);
7904
8231
  const face = filesFace(createList(remote), createWatch(remote));
7905
- const inject = (sessionId, actions) => ({
7906
- face: face(sessionId, actions),
7907
- openFile: (address) => {
7908
- ctx.get("sidebarRight")?.openResourceIn?.(sessionId, address);
7909
- }
7910
- });
8232
+ const inject = (sessionId, actions) => {
8233
+ const selection = (0, _deepseek_ai_dsh_client_store.createSnapshotStore)({});
8234
+ const takeovers = /* @__PURE__ */ new Map();
8235
+ const endTakeover = (tabId) => {
8236
+ const held = takeovers.get(tabId);
8237
+ held?.observer?.disconnect();
8238
+ held?.controller.abort();
8239
+ takeovers.delete(tabId);
8240
+ };
8241
+ return {
8242
+ face: face(sessionId, actions),
8243
+ openFile: (address) => {
8244
+ ctx.get("sidebarRight")?.openResourceIn?.(sessionId, address);
8245
+ },
8246
+ hooks: { treeSelection: selection },
8247
+ selectRoot: (tabId, root) => {
8248
+ const next = { ...selection.getSnapshot() };
8249
+ if (root === "primary") delete next[tabId];
8250
+ else next[tabId] = root;
8251
+ selection.set(next);
8252
+ },
8253
+ takeover: {
8254
+ signal: (tabId, root, section) => {
8255
+ let held = takeovers.get(tabId);
8256
+ if (held === void 0 || held.root !== root || held.controller.signal.aborted) {
8257
+ held?.observer?.disconnect();
8258
+ held?.controller.abort();
8259
+ held = {
8260
+ controller: new AbortController(),
8261
+ root,
8262
+ section: void 0,
8263
+ observer: void 0
8264
+ };
8265
+ takeovers.set(tabId, held);
8266
+ }
8267
+ if (section !== void 0 && held.section !== section) {
8268
+ held.section = section;
8269
+ held.observer?.disconnect();
8270
+ held.observer = new MutationObserver(() => {
8271
+ if (!section.isConnected) endTakeover(tabId);
8272
+ });
8273
+ held.observer.observe(document, {
8274
+ childList: true,
8275
+ subtree: true
8276
+ });
8277
+ }
8278
+ if (held.section !== void 0 && !held.section.isConnected) endTakeover(tabId);
8279
+ return held.controller.signal;
8280
+ },
8281
+ end: endTakeover
8282
+ }
8283
+ };
8284
+ };
7911
8285
  return {
7912
8286
  store,
7913
8287
  inject
@@ -7924,19 +8298,22 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7924
8298
  */
7925
8299
  function createFilesTreeSurface(props, t) {
7926
8300
  if (!filesTreeSurfaceSupported()) return void 0;
7927
- const { sessionId, useStore, actions, face, openFile } = props;
7928
- if (face === void 0 || openFile === void 0 || useStore === void 0 || actions === void 0) return void 0;
8301
+ const { sessionId, useStore, actions, face, openFile, useTreeSelection, selectRoot, takeover } = props;
8302
+ if (face === void 0 || openFile === void 0 || useStore === void 0 || actions === void 0 || useTreeSelection === void 0 || selectRoot === void 0 || takeover === void 0) return void 0;
7929
8303
  return {
7930
8304
  sessionId,
7931
8305
  useStore,
7932
8306
  actions,
7933
8307
  face,
7934
8308
  openFile,
8309
+ useTreeSelection,
8310
+ selectRoot,
8311
+ takeover,
7935
8312
  t
7936
8313
  };
7937
8314
  }
7938
8315
  //#endregion
7939
- //#region node_modules/.pnpm/@deepseek-ai+dsh-host-open-in-app@0.2.2-alpha.2_@deepseek-ai+cordis@4.0.6-alpha.1_@deep_d76f08f4e35ff3a756a48d7d013379f6/node_modules/@deepseek-ai/dsh-host-open-in-app/lib/types/shared.js
8316
+ //#region node_modules/.pnpm/@deepseek-ai+dsh-host-open-in-app@0.2.2-rc.1_@deepseek-ai+cordis@4.1.0_@deepseek-ai+dsh_33f3d65190f3ff17e4e9438df89ed28d/node_modules/@deepseek-ai/dsh-host-open-in-app/lib/types/shared.js
7940
8317
  var shared_exports = /* @__PURE__ */ __exportAll({
7941
8318
  OPEN_IN_APP_APPS_PATH: () => OPEN_IN_APP_APPS_PATH,
7942
8319
  OPEN_IN_APP_APPS_ROUTE: () => OPEN_IN_APP_APPS_ROUTE,
@@ -9574,9 +9951,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9574
9951
  * the same element's `closest('[data-dockkit-content]')` — the dockkit
9575
9952
  * tabHost section — with a generated fallback where the hook is absent.
9576
9953
  *
9577
- * The selected root is component state on purpose: every tab render
9578
- * occurrence gets its own, and reopening the pane resets to the primary
9579
- * root. There is no cross-tab or persisted selection.
9954
+ * The selected root lives in the inject product's session-scoped snapshot,
9955
+ * keyed by that dockkit tab id — component state could not survive the
9956
+ * remount every file open causes (the opened tab takes the pane, the Files
9957
+ * body unmounts with the whole occupant subtree). One selection per tab;
9958
+ * session switches mint a fresh inject product, so no selection crosses
9959
+ * sessions, and a registry eviction reconciles it back to primary.
9580
9960
  *
9581
9961
  * @module @dsh-electron/dsh-plugin-multi-root-workspace/client/RootSwitcher
9582
9962
  */
@@ -9587,17 +9967,20 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9587
9967
  * path stays readable and hoverable, only the dir/file typography differs.
9588
9968
  */
9589
9969
  const PathLabel = maybeComponent(["PathLabel"]);
9970
+ /** Selector hook for a composition without the fork: nothing was ever selectable. */
9971
+ const noTreeSelection = () => void 0;
9590
9972
  /** The Files-header switcher: root menu, dialog entry, and the portal tree. */
9591
9973
  function RootSwitcher(props) {
9592
9974
  const { panel, t, primaryPath, filesTree } = props;
9593
9975
  const [menuOpen, setMenuOpen] = (0, react.useState)(false);
9594
9976
  const [dialogOpen, setDialogOpen] = (0, react.useState)(false);
9595
9977
  const [view, setView] = (0, react.useState)(void 0);
9596
- const [selected, setSelected] = (0, react.useState)("primary");
9597
9978
  const [takeover, setTakeover] = (0, react.useState)(null);
9598
9979
  const wrapperRef = (0, react.useRef)(null);
9599
9980
  const fallbackTabId = (0, react.useRef)(void 0);
9981
+ const sectionRef = (0, react.useRef)(null);
9600
9982
  const [pane, setPane] = (0, react.useState)(null);
9983
+ const [tabId, setTabId] = (0, react.useState)(void 0);
9601
9984
  const mounted = (0, react.useRef)(true);
9602
9985
  const requestEpoch = (0, react.useRef)(0);
9603
9986
  (0, react.useEffect)(() => {
@@ -9608,8 +9991,18 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9608
9991
  };
9609
9992
  }, []);
9610
9993
  (0, react.useEffect)(() => {
9611
- setPane(wrapperRef.current?.closest("[data-files-root]") ?? null);
9994
+ const wrapper = wrapperRef.current;
9995
+ sectionRef.current = wrapper?.closest("[data-dockkit-content]") ?? null;
9996
+ const content = sectionRef.current?.dataset.dockkitContent;
9997
+ setTabId(content !== void 0 && content !== "" ? content : fallbackTabId.current ??= `mrfw-${Math.random().toString(36).slice(2)}`);
9998
+ setPane(wrapper?.closest("[data-files-page]") ?? wrapper?.closest("[data-files-root]") ?? null);
9612
9999
  }, []);
10000
+ const selected = (filesTree?.useTreeSelection ?? noTreeSelection)((selection) => tabId === void 0 ? void 0 : selection[tabId]) ?? "primary";
10001
+ const selectRoot = filesTree?.selectRoot;
10002
+ const setSelected = (0, react.useCallback)((next) => {
10003
+ if (selectRoot === void 0 || tabId === void 0) return;
10004
+ selectRoot(tabId, next);
10005
+ }, [selectRoot, tabId]);
9613
10006
  const refresh = (0, react.useCallback)(async () => {
9614
10007
  if (panel === void 0) {
9615
10008
  setView(void 0);
@@ -9647,39 +10040,48 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9647
10040
  }, [
9648
10041
  props.subscribeSession,
9649
10042
  props.sessionId,
9650
- refresh
10043
+ refresh,
10044
+ setSelected
9651
10045
  ]);
9652
10046
  (0, react.useEffect)(() => {
9653
- if (selected === "primary") return;
9654
- const current = view?.roots.find((root) => root.id === selected.id);
10047
+ if (selected === "primary" || view === void 0) return;
10048
+ const current = view.roots.find((root) => root.id === selected.id);
9655
10049
  if (current === void 0 || current.state !== "available") {
9656
10050
  setSelected("primary");
9657
10051
  return;
9658
10052
  }
9659
10053
  if (current !== selected) setSelected(current);
9660
- }, [view, selected]);
10054
+ }, [
10055
+ view,
10056
+ selected,
10057
+ setSelected
10058
+ ]);
9661
10059
  const roots = (0, react.useMemo)(() => view?.roots ?? [], [view]);
9662
10060
  const primaryLabel = view?.primaryName ?? basename(view?.primaryRoot ?? primaryPath);
9663
10061
  const triggerLabel = selected === "primary" ? primaryLabel : displayNameOf(selected);
9664
10062
  const triggerPath = selected === "primary" ? view?.primaryRoot ?? primaryPath : selected.path;
9665
10063
  const treeVisible = selected !== "primary" && panel !== void 0 && pane !== null && filesTree !== void 0;
9666
10064
  const selectedPath = selected === "primary" ? void 0 : selected.path;
10065
+ const takeoverCtl = filesTree?.takeover;
9667
10066
  (0, react.useEffect)(() => {
9668
- if (!treeVisible || selectedPath === void 0) {
10067
+ if (!treeVisible || selectedPath === void 0 || tabId === void 0 || takeoverCtl === void 0) {
9669
10068
  setTakeover(null);
9670
10069
  return;
9671
10070
  }
9672
- const controller = new AbortController();
9673
- const tabId = (wrapperRef.current?.closest("[data-dockkit-content]")?.dataset.dockkitContent ?? "") || (fallbackTabId.current ??= `mrfw-${Math.random().toString(36).slice(2)}`);
9674
10071
  setTakeover({
9675
- signal: controller.signal,
10072
+ signal: takeoverCtl.signal(tabId, selectedPath, sectionRef.current ?? void 0),
9676
10073
  tabId,
9677
10074
  root: selectedPath
9678
10075
  });
9679
10076
  return () => {
9680
- controller.abort();
10077
+ if (mounted.current || sectionRef.current === null) takeoverCtl.end(tabId);
9681
10078
  };
9682
- }, [treeVisible, selectedPath]);
10079
+ }, [
10080
+ treeVisible,
10081
+ selectedPath,
10082
+ tabId,
10083
+ takeoverCtl
10084
+ ]);
9683
10085
  const items = (0, react.useMemo)(() => [{
9684
10086
  id: "primary",
9685
10087
  label: `${primaryLabel} · ${t("panel.primary")}`,
@@ -9792,7 +10194,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9792
10194
  start: filesTree.face.start,
9793
10195
  toggle: filesTree.face.toggle,
9794
10196
  onOpen: filesTree.openFile,
9795
- t: filesTree.t
10197
+ t: filesTree.t,
10198
+ nativeTree: pane.hasAttribute("data-files-page") ? props.nativeTree : void 0
9796
10199
  }), pane) : null,
9797
10200
  dialogOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspaceFoldersDialog, {
9798
10201
  panel: props.panel,
@@ -9897,7 +10300,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9897
10300
  inject: filesTreeSeats.inject
9898
10301
  }, function FilesActionsOccupant(props) {
9899
10302
  const absolutePath = props?.absolutePath;
9900
- const paneSessionId = () => props?.sessionId ?? sessionId();
10303
+ const composedSessionId = props?.sessionId;
10304
+ const paneSessionId = (0, react.useCallback)(() => composedSessionId ?? sessionId(), [composedSessionId]);
9901
10305
  const filesTree = createFilesTreeSurface(props, filesTreeT);
9902
10306
  return absolutePath === void 0 ? (0, react.createElement)(WorkspaceFoldersAction, {
9903
10307
  panel,
@@ -9914,7 +10318,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9914
10318
  subscribeSession,
9915
10319
  t,
9916
10320
  openInApp: openInApp?.adapter,
9917
- filesTree
10321
+ filesTree,
10322
+ nativeTree: nativeTreeRenderer(props.renderFactorySlot)
9918
10323
  });
9919
10324
  }));
9920
10325
  }