@dsh-electron/dsh-plugin-multi-root-workspace 0.1.13 → 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 (42) hide show
  1. package/CHANGELOG.en.md +17 -1
  2. package/CHANGELOG.md +17 -1
  3. package/README.en.md +8 -8
  4. package/README.md +8 -8
  5. package/detail/DETAIL.en.md +1 -0
  6. package/detail/DETAIL.md +1 -0
  7. package/detail/preview.svg +1 -0
  8. package/detail/preview.zh.svg +1 -0
  9. package/lib/client.js +392 -63
  10. package/lib/client.js.map +1 -1
  11. package/lib/{command-B9855H7y.js → command-Dcz6nULo.js} +25 -3
  12. package/lib/command.js +1 -1
  13. package/lib/{compat-Bg2HFVMX.js → compat-veDqCOoa.js} +3 -1
  14. package/lib/compat.js +1 -1
  15. package/lib/{fs-CfzJq2wd.js → fs-Uws0ZpmV.js} +38 -0
  16. package/lib/fs.js +1 -1
  17. package/lib/index.js +4 -4
  18. package/lib/{native-win32-2waCRkPI.js → native-win32-CRNXr11U.js} +2 -1
  19. package/lib/{protocol-CJOvAvOS.js → protocol-CnR1KTXy.js} +49 -7
  20. package/lib/{registry-Bv431TfO.js → registry-DR_sMuvS.js} +3 -3
  21. package/lib/{registry-lease-win32-DVTq9d4D.js → registry-lease-win32-DDO-c7ig.js} +2 -1
  22. package/lib/registry.js +2 -2
  23. package/lib/types/client/RootSwitcher.d.ts +2 -0
  24. package/lib/types/client/auto-review-settings.d.ts +18 -0
  25. package/lib/types/client/files-tree/FilesTree.d.ts +4 -1
  26. package/lib/types/client/files-tree/locales.d.ts +1 -1
  27. package/lib/types/client/locales.d.ts +12 -0
  28. package/lib/types/client/open-in-app/locales.d.ts +1 -1
  29. package/lib/types/client/skills-settings.d.ts +12 -0
  30. package/lib/types/client/styles.d.ts +1 -1
  31. package/lib/types/compat/auto-review-client.d.ts +58 -0
  32. package/lib/types/compat/connection-rpc.d.ts +12 -0
  33. package/lib/types/compat/dsh-version.d.ts +1 -1
  34. package/lib/types/compat/files-tree.d.ts +36 -0
  35. package/lib/types/compat/fs-patch.d.ts +6 -0
  36. package/lib/types/compat/koffi.d.ts +2 -0
  37. package/lib/types/fs.d.ts +3 -1
  38. package/locale/en.json +7 -0
  39. package/locale/zh.json +7 -0
  40. package/package.json +89 -82
  41. package/src/auto-review/README.en.md +1 -1
  42. package/src/auto-review/README.md +1 -1
@@ -2,11 +2,33 @@ import { t as effectiveDirectory } from "./session-directory-BLOstut1.js";
2
2
  import { r as canonicalPath$1 } from "./windows-runner-contract-B5R2K3mM.js";
3
3
  import { i as canonicalRoot, o as entryRootRef, p as resolveRootRef, r as availableRoots, t as RootValidationError } from "./roots-Ca_W1S9n.js";
4
4
  import { c as skillsRequestSchema, i as parsePanelCall, r as effectiveRootAccess, s as SKILLS_ENDPOINTS, t as PANEL_CHANNEL } from "./contract-CStSEOcK.js";
5
- import { n as AuthorityRpcError } from "./protocol-CJOvAvOS.js";
5
+ import { n as AuthorityRpcError } from "./protocol-CnR1KTXy.js";
6
6
  import { closeSync, lstatSync, openSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
7
7
  import { isAbsolute, join } from "node:path";
8
8
  import "@deepseek-ai/cordis";
9
9
  import { canonicalPath } from "@deepseek-ai/dsh-sandbox";
10
+ //#region src/compat/connection-rpc.ts
11
+ /**
12
+ * Newer Cordis keeps a service getter's definition site when tracing it, so
13
+ * even reading Connection from the root can make its RPC getter capture the
14
+ * provider's context (which does not inject webServer). Invoke the public
15
+ * accessor with the actual consumer context instead. A data property is used
16
+ * unchanged for simpler Connection implementations. The service is not
17
+ * modified and the route remains owned by the injecting fiber.
18
+ */
19
+ function connectionRpcFor(ctx, connection) {
20
+ for (let proto = connection; proto !== null; proto = Object.getPrototypeOf(proto)) {
21
+ const descriptor = Object.getOwnPropertyDescriptor(proto, "rpc");
22
+ if (descriptor === void 0) continue;
23
+ if (typeof descriptor.get !== "function") return connection.rpc;
24
+ const receiver = new Proxy(connection, { get(target, key, proxy) {
25
+ return key === "ctx" ? ctx : Reflect.get(target, key, proxy);
26
+ } });
27
+ return Reflect.apply(descriptor.get, receiver, []);
28
+ }
29
+ return connection.rpc;
30
+ }
31
+ //#endregion
10
32
  //#region src/profile-isolation.ts
11
33
  /**
12
34
  * Profile pnpm import isolation — the host-side maintenance behind
@@ -751,12 +773,12 @@ function apply(ctx, config = {}) {
751
773
  handler: async (invocation) => await runCommand(ctx, invocation, config)
752
774
  });
753
775
  ctx.inject(["connection", "webServer"], (connectionCtx) => {
754
- const connection = connectionCtx.root.get("connection");
776
+ const connection = connectionCtx.get("connection");
755
777
  if (connection === void 0) {
756
778
  ctx.logger.warn("multi-root: connection resolved without the service; the panel channel is not mounted");
757
779
  return;
758
780
  }
759
- const dispose = connection.rpc.handle(PANEL_CHANNEL, async (endpoint, payload, signal) => await dispatchPanelRequest(ctx, endpoint, payload, signal));
781
+ const dispose = connectionRpcFor(connectionCtx, connection).handle(PANEL_CHANNEL, async (endpoint, payload, signal) => await dispatchPanelRequest(ctx, endpoint, payload, signal));
760
782
  return () => {
761
783
  dispose();
762
784
  };
package/lib/command.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as renderRootsReport, c as revealArgv, i as parseFoldersCommand, n as apply, o as resolvePanelPrimaryRoot, r as inject, s as revealAccepted, t as COMMAND_NAME } from "./command-B9855H7y.js";
1
+ import { a as renderRootsReport, c as revealArgv, i as parseFoldersCommand, n as apply, o as resolvePanelPrimaryRoot, r as inject, s as revealAccepted, t as COMMAND_NAME } from "./command-Dcz6nULo.js";
2
2
  export { COMMAND_NAME, apply, inject, parseFoldersCommand, renderRootsReport, resolvePanelPrimaryRoot, revealAccepted, revealArgv };
@@ -54,7 +54,9 @@ const SUPPORTED_DSH_RELEASES = [
54
54
  "0.2.1-rc.3",
55
55
  "0.2.1-rc.4",
56
56
  "0.2.1-rc.5",
57
- "0.2.2-alpha.2"
57
+ "0.2.2-alpha.2",
58
+ "0.2.2-alpha.3",
59
+ "0.2.2-rc.1"
58
60
  ];
59
61
  /**
60
62
  * Packages whose absence means this plugin cannot function at all: every one of
package/lib/compat.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as inject, i as assertSupportedInstallation, n as MultiRootCompatService, r as apply, t as DshCompatUnsupportedError } from "./compat-Bg2HFVMX.js";
1
+ import { a as inject, i as assertSupportedInstallation, n as MultiRootCompatService, r as apply, t as DshCompatUnsupportedError } from "./compat-veDqCOoa.js";
2
2
  export { DshCompatUnsupportedError, MultiRootCompatService, apply, assertSupportedInstallation, inject };
@@ -5,6 +5,30 @@ import { canonicalPath, writableRoots } from "@deepseek-ai/dsh-sandbox";
5
5
  import { stat } from "node:fs/promises";
6
6
  import { FsError } from "@deepseek-ai/dsh-fs";
7
7
  import { LocalFileSystem } from "@deepseek-ai/dsh-fs-local";
8
+ //#region src/compat/fs-patch.ts
9
+ /**
10
+ * Adapt the mutation methods introduced by newer filesystem backends.
11
+ * Older supported backends do not expose these methods at all.
12
+ */
13
+ /** Install only methods present on the actual backend, preserving the caller as `this`. */
14
+ function installAdditionalMutations(prototype, fence) {
15
+ for (const method of [
16
+ "patchText",
17
+ "deleteFile",
18
+ "moveFile"
19
+ ]) {
20
+ const inherited = Reflect.get(LocalFileSystem.prototype, method);
21
+ if (typeof inherited !== "function") continue;
22
+ Object.defineProperty(prototype, method, {
23
+ configurable: true,
24
+ writable: true,
25
+ value: function(...args) {
26
+ return fence(this, method, args, (next) => Reflect.apply(inherited, this, next));
27
+ }
28
+ });
29
+ }
30
+ }
31
+ //#endregion
8
32
  //#region src/containment.ts
9
33
  const MISSING_CODES = /* @__PURE__ */ new Set(["ENOENT", "ENOTDIR"]);
10
34
  function isMissing(error) {
@@ -67,6 +91,9 @@ async function isPathUnder(path, root, caseSensitive = process.platform !== "win
67
91
  */
68
92
  var MultiRootFileSystem = class extends LocalFileSystem {
69
93
  static inject = ["sandboxPolicy", "multiRootScope"];
94
+ static {
95
+ installAdditionalMutations(this.prototype, (receiver, method, args, invoke) => receiver.additionalMutation(method, args, invoke));
96
+ }
70
97
  defaultMode;
71
98
  constructor(ctx, config) {
72
99
  super(ctx, config);
@@ -107,6 +134,17 @@ var MultiRootFileSystem = class extends LocalFileSystem {
107
134
  const policy = structuredClone(sandboxPolicy ?? this.ctx.sandboxPolicy.resolve());
108
135
  return this.withOperation(policy, "fs-edit", async (scope) => super.editText(await this.checkedTarget(target, policy, scope), edit, expected, signal));
109
136
  }
137
+ /** Fence every endpoint of a patch, delete, or move under one admission. */
138
+ async additionalMutation(method, args, invoke) {
139
+ const policyIndex = method === "deleteFile" ? 3 : 4;
140
+ const policy = structuredClone(args[policyIndex] ?? this.ctx.sandboxPolicy.resolve());
141
+ return this.withOperation(policy, method === "patchText" ? "fs-edit" : "fs-write", async (scope) => {
142
+ const checked = [...args];
143
+ checked[0] = await this.checkedTarget(args[0], policy, scope);
144
+ if (method === "moveFile") checked[1] = await this.checkedTarget(args[1], policy, scope);
145
+ return await invoke(checked);
146
+ });
147
+ }
110
148
  /** The provider must also work without the gated registry (ADR-0009). */
111
149
  async withOperation(policy, kind, body) {
112
150
  const registry = this.ctx.get("multiRootRegistry");
package/lib/fs.js CHANGED
@@ -1,3 +1,3 @@
1
1
  import "./providers.js";
2
- import { t as MultiRootFileSystem } from "./fs-CfzJq2wd.js";
2
+ import { t as MultiRootFileSystem } from "./fs-Uws0ZpmV.js";
3
3
  export { MultiRootFileSystem, MultiRootFileSystem as default };
package/lib/index.js CHANGED
@@ -1,15 +1,15 @@
1
1
  import { i as renderInstructions, n as instructionsApi, t as createInstructionMessage } from "./llm-message-fYORacvh.js";
2
2
  import { a as composeInstructionMessage, r as PLUGIN_SOURCE, t as DEFAULT_MAX_BYTES } from "./instructions-KsUwUFE0.js";
3
3
  import { a as classifyStoredRoots, c as indexedStatuses, i as canonicalRoot, l as isCanonicallyUnder, m as validateRootCandidate, n as additionalRootId, p as resolveRootRef, r as availableRoots, s as expandRootInput, t as RootValidationError, u as removeStatusAt } from "./roots-Ca_W1S9n.js";
4
- import { _ as classifyInstallation, c as agentPtyMarkerVerdict, d as HOST_SERVICE_PACKAGES, f as OPTIONAL_CORE_PACKAGES, g as SUPPORTED_DSH_RELEASES, h as RUNTIME_OPTIONAL_PACKAGES, i as assertSupportedInstallation, l as assertAgentPtyMarker, m as RUNNER_ANCHORED_PACKAGES, n as MultiRootCompatService, o as AGENT_PTY_MARKER, p as REQUIRED_CORE_PACKAGES, s as PtyMarkerContractError, t as DshCompatUnsupportedError, u as CORE_PACKAGES, v as compatEnforcement, y as readInstalledVersion } from "./compat-Bg2HFVMX.js";
4
+ import { _ as classifyInstallation, c as agentPtyMarkerVerdict, d as HOST_SERVICE_PACKAGES, f as OPTIONAL_CORE_PACKAGES, g as SUPPORTED_DSH_RELEASES, h as RUNTIME_OPTIONAL_PACKAGES, i as assertSupportedInstallation, l as assertAgentPtyMarker, m as RUNNER_ANCHORED_PACKAGES, n as MultiRootCompatService, o as AGENT_PTY_MARKER, p as REQUIRED_CORE_PACKAGES, s as PtyMarkerContractError, t as DshCompatUnsupportedError, u as CORE_PACKAGES, v as compatEnforcement, y as readInstalledVersion } from "./compat-veDqCOoa.js";
5
5
  import { l as widenConfined } from "./dialects-Dr4rDR-H.js";
6
6
  import { MULTI_ROOT_PROVIDER_BRAND, MultiRootProviders, brandMultiRootProvider, isMultiRootProvider } from "./providers.js";
7
- import { t as MultiRootFileSystem } from "./fs-CfzJq2wd.js";
7
+ import { t as MultiRootFileSystem } from "./fs-Uws0ZpmV.js";
8
8
  import { t as MultiRootSandboxProvider } from "./sandbox-C-vCJCp3.js";
9
9
  import { a as parseRevealedView, i as parsePanelCall, n as PANEL_ENDPOINTS, o as parseRootsView, t as PANEL_CHANNEL } from "./contract-CStSEOcK.js";
10
10
  import { MultiRootScopeService, renderWorkspaceRootsContext, sanitizeAdditionalRoots } from "./scope.js";
11
- import { a as MultiRootRegistry, c as multiRootDomainSpec, i as MAX_ALIAS_LENGTH, l as RegistryAuthorityLease, n as DOMAIN_NAME, o as REGISTRY_CONTENDED_MESSAGE, r as MAX_ADDITIONAL_ROOTS_CEILING, t as DEFAULT_MAX_ADDITIONAL_ROOTS, u as RegistryLeaseContendedError } from "./registry-Bv431TfO.js";
12
- import { a as renderRootsReport, c as revealArgv, i as parseFoldersCommand, o as resolvePanelPrimaryRoot, t as COMMAND_NAME } from "./command-B9855H7y.js";
11
+ import { a as MultiRootRegistry, c as multiRootDomainSpec, i as MAX_ALIAS_LENGTH, l as RegistryAuthorityLease, n as DOMAIN_NAME, o as REGISTRY_CONTENDED_MESSAGE, r as MAX_ADDITIONAL_ROOTS_CEILING, t as DEFAULT_MAX_ADDITIONAL_ROOTS, u as RegistryLeaseContendedError } from "./registry-DR_sMuvS.js";
12
+ import { a as renderRootsReport, c as revealArgv, i as parseFoldersCommand, o as resolvePanelPrimaryRoot, t as COMMAND_NAME } from "./command-Dcz6nULo.js";
13
13
  //#region src/index.ts
14
14
  /**
15
15
  * Carrier plugin face for the bare-package-name loader row (see the module
@@ -1,10 +1,11 @@
1
+ import { c as loadHostKoffi } from "./protocol-CnR1KTXy.js";
1
2
  import { EventEmitter } from "node:events";
2
3
  //#region src/authority/native-win32.ts
3
4
  /** Windows local pipes: tokens, never a caller-supplied PID or a bearer file. */
4
5
  var RestrictedRegistryPeerError = class extends Error {};
5
6
  let apiPromise;
6
7
  async function load() {
7
- const koffi = (await import("koffi")).default;
8
+ const koffi = await loadHostKoffi();
8
9
  const kernel = koffi.load("kernel32.dll"), advapi = koffi.load("advapi32.dll");
9
10
  const k = (name, ret, args) => kernel.func("__stdcall", name, ret, args);
10
11
  const a = (name, ret, args) => advapi.func("__stdcall", name, ret, args);
@@ -1,8 +1,50 @@
1
+ import { createRequire } from "node:module";
1
2
  import { closeSync, lstatSync, mkdirSync, readFileSync, realpathSync, statSync, unlinkSync } from "node:fs";
2
3
  import { createHash, randomUUID } from "node:crypto";
3
- import { join } from "node:path";
4
+ import { dirname, join } from "node:path";
5
+ import { pathToFileURL } from "node:url";
4
6
  import { z } from "zod";
5
7
  import { Socket } from "node:net";
8
+ //#region src/compat/koffi.ts
9
+ /**
10
+ * Koffi must be the host's own copy, not this package's `dependencies` entry:
11
+ * `dsh plugin add` installs a private koffi beside the one the host already
12
+ * loaded, and two `koffi.node` images in one process SIGBUS on darwin-arm64
13
+ * pointer authentication (packed-install smoke, 2026-11-01). `dsh-fs-local`
14
+ * carries koffi on every supported release, so anchoring at it yields the
15
+ * single binary the host itself loaded.
16
+ *
17
+ * Two ways to reach that same binary, in order:
18
+ * 1. `require('koffi')` from the host anchor — the live CJS `module.exports`.
19
+ * The host occasionally exposes a partial `{}` exports object for koffi's
20
+ * CJS entry (mid-evaluation or an intercepted require in the addon loader),
21
+ * so the result is validated instead of trusted.
22
+ * 2. The host koffi's own ESM entry (`index.js` beside the resolved CJS
23
+ * entry) — a separate module instance over the same `koffi.node`, immune
24
+ * to a broken CJS cache entry and never a second native image.
25
+ * Only when no host tree resolves at all does the package-local copy load.
26
+ */
27
+ const pick = (candidate) => {
28
+ let cur = candidate;
29
+ for (let i = 0; i < 3; i++) {
30
+ if (typeof cur?.load === "function") return cur;
31
+ cur = cur?.default;
32
+ }
33
+ };
34
+ async function loadHostKoffi() {
35
+ try {
36
+ const anchor = createRequire(import.meta.url).resolve("@deepseek-ai/dsh-fs-local/package.json");
37
+ const hostRequire = createRequire(anchor);
38
+ const cjs = pick(hostRequire("koffi"));
39
+ if (cjs !== void 0) return cjs;
40
+ const esm = pick(await import(pathToFileURL(join(dirname(hostRequire.resolve("koffi")), "index.js")).href));
41
+ if (esm !== void 0) return esm;
42
+ } catch {}
43
+ const own = pick(await import("koffi"));
44
+ if (own !== void 0) return own;
45
+ return (await import("koffi")).default;
46
+ }
47
+ //#endregion
6
48
  //#region src/authority/native.ts
7
49
  /** Kernel-authenticated local IPC. No credential file and no inherited capability. */
8
50
  function storeIdentity(root) {
@@ -34,7 +76,7 @@ function storeIdentity(root) {
34
76
  }
35
77
  let posixPromise;
36
78
  async function loadPosix() {
37
- const koffi = (await import("koffi")).default;
79
+ const koffi = await loadHostKoffi();
38
80
  const libc = koffi.load(process.platform === "darwin" ? "/usr/lib/libSystem.B.dylib" : "libc.so.6");
39
81
  const bind = (name, ret, args) => libc.func(name, ret, args);
40
82
  const socketpair = bind("socketpair", "int", [
@@ -170,7 +212,7 @@ function posix$1() {
170
212
  return posixPromise ??= loadPosix();
171
213
  }
172
214
  async function connectNative(endpoint) {
173
- if (process.platform === "win32") return (await import("./native-win32-2waCRkPI.js")).connectWindows(endpoint);
215
+ if (process.platform === "win32") return (await import("./native-win32-CRNXr11U.js")).connectWindows(endpoint);
174
216
  const api = await posix$1(), address = api.address(endpoint), fd = api.open();
175
217
  if (api.connect(fd, address, address.length) !== 0) {
176
218
  closeSync(fd);
@@ -180,7 +222,7 @@ async function connectNative(endpoint) {
180
222
  }
181
223
  /** Called only by the kernel lease holder. Never removes another epoch's socket. */
182
224
  async function listenNative(endpoint, accepted) {
183
- if (process.platform === "win32") return (await import("./native-win32-2waCRkPI.js")).listenWindows(endpoint, accepted);
225
+ if (process.platform === "win32") return (await import("./native-win32-CRNXr11U.js")).listenWindows(endpoint, accepted);
184
226
  const api = await posix$1(), address = api.address(endpoint), fd = api.open();
185
227
  try {
186
228
  unlinkSync(endpoint);
@@ -222,14 +264,14 @@ async function listenNative(endpoint, accepted) {
222
264
  }
223
265
  /** Capability probe for native tests and diagnostics; not an authorization bypass. */
224
266
  async function nativeHostAvailability() {
225
- if (process.platform === "win32") return (await import("./native-win32-2waCRkPI.js")).windowsHostAvailability();
267
+ if (process.platform === "win32") return (await import("./native-win32-CRNXr11U.js")).windowsHostAvailability();
226
268
  if (process.platform === "linux") {
227
269
  if (!/^NoNewPrivs:\s+0$/m.test(readFileSync("/proc/self/status", "utf8"))) return {
228
270
  available: false,
229
271
  reason: "the Host itself has NoNewPrivs; shared IPC refuses restricted peers"
230
272
  };
231
273
  } else if (process.platform === "darwin") {
232
- if ((await import("koffi")).default.load("/usr/lib/libsandbox.dylib").func("int sandbox_check(int pid, const char *operation, int filter)")(process.pid, null, 0) !== 0) return {
274
+ if ((await loadHostKoffi()).load("/usr/lib/libsandbox.dylib").func("int sandbox_check(int pid, const char *operation, int filter)")(process.pid, null, 0) !== 0) return {
233
275
  available: false,
234
276
  reason: "the Host itself is sandboxed; shared IPC refuses sandboxed peers"
235
277
  };
@@ -649,4 +691,4 @@ function parseArguments(method, args) {
649
691
  return argumentSchemas[method].parse(args);
650
692
  }
651
693
  //#endregion
652
- export { snapshotSchema as a, parseArguments as i, AuthorityRpcError as n, nativeHostAvailability as o, AuthorityServer as r, storeIdentity as s, AuthorityClient as t };
694
+ export { snapshotSchema as a, loadHostKoffi as c, parseArguments as i, AuthorityRpcError as n, nativeHostAvailability as o, AuthorityServer as r, storeIdentity as s, AuthorityClient as t };
@@ -2,7 +2,7 @@ import { a as runPrimaryOperation, n as freezeOperationScope, o as runRegistryOp
2
2
  import { i as isWindowsNetworkOrDevicePath } from "./windows-runner-contract-B5R2K3mM.js";
3
3
  import { a as classifyStoredRoots, d as reservedRootBoundary, f as resolveRootIndex, i as canonicalRoot, m as validateRootCandidate, n as additionalRootId, r as availableRoots, s as expandRootInput, t as RootValidationError, u as removeStatusAt } from "./roots-Ca_W1S9n.js";
4
4
  import { r as validateAliasId, t as migrateRootSkills } from "./metadata-DgqSdVX_.js";
5
- import { a as snapshotSchema, i as parseArguments, r as AuthorityServer, s as storeIdentity, t as AuthorityClient } from "./protocol-CJOvAvOS.js";
5
+ import { a as snapshotSchema, i as parseArguments, r as AuthorityServer, s as storeIdentity, t as AuthorityClient } from "./protocol-CnR1KTXy.js";
6
6
  import { createRequire } from "node:module";
7
7
  import { closeSync, fstatSync, mkdirSync, openSync, statSync } from "node:fs";
8
8
  import { randomUUID } from "node:crypto";
@@ -265,7 +265,7 @@ var RegistryAuthorityLease = class RegistryAuthorityLease {
265
265
  mode: 448
266
266
  });
267
267
  if (process.platform === "win32") {
268
- const { acquireLockHandleWin32 } = await import("./registry-lease-win32-DVTq9d4D.js");
268
+ const { acquireLockHandleWin32 } = await import("./registry-lease-win32-DDO-c7ig.js");
269
269
  let handle;
270
270
  try {
271
271
  handle = await acquireLockHandleWin32(leasePath);
@@ -317,7 +317,7 @@ var RegistryAuthorityLease = class RegistryAuthorityLease {
317
317
  if (this.released) return;
318
318
  this.released = true;
319
319
  if (this.held.kind === "win32") {
320
- const { releaseLockHandleWin32 } = await import("./registry-lease-win32-DVTq9d4D.js");
320
+ const { releaseLockHandleWin32 } = await import("./registry-lease-win32-DDO-c7ig.js");
321
321
  await releaseLockHandleWin32(this.held.handle);
322
322
  return;
323
323
  }
@@ -1,3 +1,4 @@
1
+ import { c as loadHostKoffi } from "./protocol-CnR1KTXy.js";
1
2
  import { realpathSync } from "node:fs";
2
3
  import { createHash } from "node:crypto";
3
4
  import { basename, dirname, join, resolve } from "node:path";
@@ -23,7 +24,7 @@ let bindings;
23
24
  /** Load kernel32 through Koffi the first time a Windows process needs it. */
24
25
  async function kernel32() {
25
26
  if (bindings !== void 0) return bindings;
26
- const library = (await import("koffi")).default.load("kernel32.dll");
27
+ const library = (await loadHostKoffi()).load("kernel32.dll");
27
28
  bindings = {
28
29
  createSemaphoreW: library.func("__stdcall", "CreateSemaphoreW", "intptr", [
29
30
  "void*",
package/lib/registry.js CHANGED
@@ -1,3 +1,3 @@
1
- import { a as MultiRootRegistry, c as multiRootDomainSpec, i as MAX_ALIAS_LENGTH, n as DOMAIN_NAME, o as REGISTRY_CONTENDED_MESSAGE, r as MAX_ADDITIONAL_ROOTS_CEILING, s as TABLE_NAME, t as DEFAULT_MAX_ADDITIONAL_ROOTS } from "./registry-Bv431TfO.js";
2
- import { o as nativeHostAvailability } from "./protocol-CJOvAvOS.js";
1
+ import { a as MultiRootRegistry, c as multiRootDomainSpec, i as MAX_ALIAS_LENGTH, n as DOMAIN_NAME, o as REGISTRY_CONTENDED_MESSAGE, r as MAX_ADDITIONAL_ROOTS_CEILING, s as TABLE_NAME, t as DEFAULT_MAX_ADDITIONAL_ROOTS } from "./registry-DR_sMuvS.js";
2
+ import { o as nativeHostAvailability } from "./protocol-CnR1KTXy.js";
3
3
  export { DEFAULT_MAX_ADDITIONAL_ROOTS, DOMAIN_NAME, MAX_ADDITIONAL_ROOTS_CEILING, MAX_ALIAS_LENGTH, MultiRootRegistry, MultiRootRegistry as default, REGISTRY_CONTENDED_MESSAGE, TABLE_NAME, multiRootDomainSpec, nativeHostAvailability };
@@ -42,6 +42,7 @@
42
42
  * @module @dsh-electron/dsh-plugin-multi-root-workspace/client/RootSwitcher
43
43
  */
44
44
  import { type ReactNode } from 'react';
45
+ import type { NativeTreeRenderer } from '../compat/files-tree.ts';
45
46
  import { type FilesTreeSurface } from './files-tree/adapter.tsx';
46
47
  import { type OpenInAppAdapter } from './open-in-app/adapter.tsx';
47
48
  import { type WorkspaceFoldersActionProps } from './WorkspaceFoldersAction.tsx';
@@ -61,6 +62,7 @@ export interface RootSwitcherProps extends Omit<WorkspaceFoldersActionProps, 'co
61
62
  * all, which is the same absence upstream's own tree produces.
62
63
  */
63
64
  readonly filesTree?: FilesTreeSurface | undefined;
65
+ readonly nativeTree?: NativeTreeRenderer | undefined;
64
66
  }
65
67
  /** The Files-header switcher: root menu, dialog entry, and the portal tree. */
66
68
  export declare function RootSwitcher(props: RootSwitcherProps): ReactNode;
@@ -0,0 +1,18 @@
1
+ import type { AutoReviewForm } from '../compat/auto-review-client.ts';
2
+ import type { Translate } from './WorkspaceFoldersAction.tsx';
3
+ /**
4
+ * The optional Auto Review row of the plugin settings card.
5
+ *
6
+ * Its switch is the Loader row's own enablement — the same write the Plugin
7
+ * Manager's row switch performs — so the card can never disagree with the row
8
+ * list about whether the companion is on. Feedback is a transient `Toast`
9
+ * banner, never inline text: state-driven messages (a failed read, an entry
10
+ * diagnostic) fire on entry and clear when their source resolves, while write
11
+ * outcomes report once and fade. The row stays hidden entirely on hosts
12
+ * without the `pluginManager` namespace or when the composition does not list
13
+ * the optional entry.
14
+ */
15
+ export declare function AutoReviewSettingsCard({ form, t }: {
16
+ form: AutoReviewForm;
17
+ t: Translate;
18
+ }): import("react").JSX.Element | null;
@@ -3,6 +3,7 @@ import type { RemoteFailure } from '@deepseek-ai/dsh-api-remotes/client';
3
3
  import type { PropsLocale, PropsStore, TranslateNS } from '@deepseek-ai/dsh-client-ui-slots';
4
4
  import type { TabId } from '@deepseek-ai/dsh-client-ui-dockkit';
5
5
  import type { SessionId } from '@deepseek-ai/dsh-session/types';
6
+ import { type NativeTreeRenderer } from '../../compat/files-tree.ts';
6
7
  import type { WorkspaceDirectoryEntry } from '@deepseek-ai/dsh-api-workspace-files/types';
7
8
  import type { FilesInjected } from './face.ts';
8
9
  import type { createFilesStore } from './store.ts';
@@ -23,6 +24,8 @@ export type FilesTreeProps = PropsStore<ReturnType<typeof createFilesStore>> & P
23
24
  readonly signal: AbortSignal;
24
25
  /** Open one `dsh-resource://file` address (the adapter's sidebarRight route). */
25
26
  readonly onOpen: (address: string) => void;
27
+ /** Optional host-controlled tree Factory, available on the newer Files page. */
28
+ readonly nativeTree?: NativeTreeRenderer | undefined;
26
29
  };
27
30
  /**
28
31
  * Order one level's entries for display: directories first, then everything
@@ -44,4 +47,4 @@ export declare function failureLine(t: TranslateNS<'sidebarFiles'>, failure: Rem
44
47
  * has opened under it — the upstream body's scroll, seed, and render logic
45
48
  * verbatim, with the root arriving as a prop instead of `useSessions(cwd)`.
46
49
  */
47
- export declare function FilesTree({ tabId, sessionId, root, signal, useStore, actions, start, toggle, onOpen, t, }: FilesTreeProps): ReactNode;
50
+ export declare function FilesTree({ tabId, sessionId, root, signal, useStore, actions, start, toggle, onOpen, t, nativeTree, }: FilesTreeProps): ReactNode;
@@ -1,7 +1,7 @@
1
1
  /** Dictionary namespace owned by upstream's plugin (verbatim). */
2
2
  export declare const NS = "sidebarFiles";
3
3
  /** Key domain of the `sidebarFiles` namespace (verbatim union of upstream's zh keys). */
4
- export type SidebarFilesKey = 'shortcut.noSession' | 'type.label' | 'guide.title' | 'guide.description' | 'loading' | 'empty' | 'truncated' | 'noWorkspace' | 'reload' | 'autoRefresh' | 'autoRefresh.enable' | 'autoRefresh.disable' | 'entry.other' | 'error.notFound' | 'error.outsideWorkspace' | 'error.notDirectory' | 'error.unavailable';
4
+ export type SidebarFilesKey = 'shortcut.noSession' | 'type.label' | 'guide.title' | 'guide.description' | 'loading' | 'empty' | 'truncated' | 'noWorkspace' | 'reload' | 'autoRefresh' | 'autoRefresh.enable' | 'autoRefresh.disable' | 'entry.other' | 'entry.noViewer' | 'navigator.show' | 'navigator.hide' | 'navigator.resize' | 'error.notFound' | 'error.outsideWorkspace' | 'error.notDirectory' | 'error.unavailable';
5
5
  declare module '@deepseek-ai/dsh-client-ui-slots' {
6
6
  interface LocaleNamespaceMap {
7
7
  /** File-tree type name, guide entry, row states, and failure lines. */
@@ -15,6 +15,12 @@ export declare const zh: {
15
15
  'skills.globalEnable': string;
16
16
  'skills.globalHint': string;
17
17
  'skills.settingsFailed': string;
18
+ 'autoReview.title': string;
19
+ 'autoReview.experimental': string;
20
+ 'autoReview.hint': string;
21
+ 'autoReview.loadFailed': string;
22
+ 'autoReview.restartRequired': string;
23
+ 'autoReview.settingsFailed': string;
18
24
  'skills.userOnly': string;
19
25
  'panel.skills': string;
20
26
  'panel.skillsEnabled': string;
@@ -104,6 +110,12 @@ export declare const en: {
104
110
  'skills.globalEnable': string;
105
111
  'skills.globalHint': string;
106
112
  'skills.settingsFailed': string;
113
+ 'autoReview.title': string;
114
+ 'autoReview.experimental': string;
115
+ 'autoReview.hint': string;
116
+ 'autoReview.loadFailed': string;
117
+ 'autoReview.restartRequired': string;
118
+ 'autoReview.settingsFailed': string;
107
119
  'skills.userOnly': string;
108
120
  'panel.skills': string;
109
121
  'panel.skillsEnabled': string;
@@ -1,7 +1,7 @@
1
1
  /** Dictionary namespace owned by upstream's plugin (verbatim). */
2
2
  export declare const NS = "open-in-app";
3
3
  /** Key domain of the `open-in-app` namespace (verbatim union of upstream's zh keys). */
4
- export type OpenInAppKey = 'open.title' | 'path.appDefault' | 'path.appsError' | 'shortcut.busy' | 'shortcut.unavailable' | 'open.tooltip' | 'path.open' | 'path.more' | 'path.reveal' | 'path.opened' | 'path.revealed' | 'path.openError' | 'path.revealError' | 'app.cursor' | 'app.vscode' | 'app.vscodeinsiders' | 'app.windsurf' | 'app.zed' | 'app.sublimetext' | 'app.xcode' | 'app.androidstudio' | 'app.intellij' | 'app.pycharm' | 'app.webstorm' | 'app.phpstorm' | 'app.goland' | 'app.rider' | 'app.rustrover' | 'app.fork' | 'app.sourcetree' | 'app.github' | 'app.tower' | 'app.gitkraken' | 'app.smartgit' | 'app.sublimemerge' | 'app.ghostty' | 'app.warp' | 'app.iterm' | 'app.kitty' | 'app.terminal' | 'app.windowsterminal' | 'app.gitbash' | 'app.gnometerminal' | 'app.konsole' | 'app.finder' | 'app.explorer' | 'app.filemanager';
4
+ export type OpenInAppKey = 'menu.openInSidebar' | 'menu.openInFloat' | 'menu.finder' | 'menu.reveal' | 'menu.defaultApp' | 'menu.external' | 'menu.copyLink' | 'menu.copyPath' | 'menu.copyDirectoryPath' | 'menu.copied' | 'menu.copyFailed' | 'open.title' | 'path.appDefault' | 'path.appsError' | 'shortcut.busy' | 'shortcut.unavailable' | 'open.tooltip' | 'path.open' | 'path.more' | 'path.reveal' | 'path.opened' | 'path.revealed' | 'path.openError' | 'path.revealError' | 'app.cursor' | 'app.vscode' | 'app.vscodeinsiders' | 'app.windsurf' | 'app.zed' | 'app.sublimetext' | 'app.xcode' | 'app.androidstudio' | 'app.intellij' | 'app.pycharm' | 'app.webstorm' | 'app.phpstorm' | 'app.goland' | 'app.rider' | 'app.rustrover' | 'app.fork' | 'app.sourcetree' | 'app.github' | 'app.tower' | 'app.gitkraken' | 'app.smartgit' | 'app.sublimemerge' | 'app.ghostty' | 'app.warp' | 'app.iterm' | 'app.kitty' | 'app.terminal' | 'app.windowsterminal' | 'app.gitbash' | 'app.gnometerminal' | 'app.konsole' | 'app.finder' | 'app.explorer' | 'app.filemanager';
5
5
  declare module '@deepseek-ai/dsh-client-ui-slots' {
6
6
  interface LocaleNamespaceMap {
7
7
  /** Session-header "open workspace in application" copy (upstream declaration). */
@@ -1,10 +1,22 @@
1
1
  import type { Context } from '@deepseek-ai/cordis';
2
2
  import { type SkillsForm } from '../compat/skills-client.ts';
3
+ import { type AutoReviewForm } from '../compat/auto-review-client.ts';
3
4
  import type { Translate } from './WorkspaceFoldersAction.tsx';
4
5
  interface CardProps {
5
6
  form: SkillsForm;
6
7
  t: Translate;
7
8
  }
8
9
  export declare function SkillsSettingsCard({ form, t }: CardProps): import("react").JSX.Element;
10
+ /**
11
+ * The bundle's one `plugins.bundle.config` cell: every plugin-level switch the
12
+ * package offers, in display order. Each row owns its own form and write path —
13
+ * Skills through its settings namespace, Auto Review through the Plugin
14
+ * Manager's Loader-row enablement — so one cell never serializes their state.
15
+ */
16
+ export declare function BundleSettingsCard({ form, autoReviewForm, t }: {
17
+ form?: SkillsForm | undefined;
18
+ autoReviewForm?: AutoReviewForm | undefined;
19
+ t: Translate;
20
+ }): import("react").JSX.Element;
9
21
  export declare function installSkillsSettingsCard(ctx: Context, t: Translate): void;
10
22
  export {};
@@ -11,4 +11,4 @@
11
11
  * @module @dsh-electron/dsh-plugin-multi-root-workspace/client/styles
12
12
  */
13
13
  /** The stylesheet text; every class is prefixed `mrfw-` to stay collision-free. */
14
- export declare const STYLES = "\n/* Sidebar footer row, mirroring the host ui-settings-general trigger: the row\n * wrapper overhangs each side by 2px (width calc(100% + 4px) against the\n * shell's 12px inline padding) so the icon's ink line lands at 18px from the\n * column edge \u2014 exactly where the Settings gear below sits. */\n.mrfw-triggerRow {\n flex: none;\n display: flex;\n align-items: center;\n gap: 8px;\n width: calc(100% + 4px);\n margin: 4px -2px;\n}\n\n.mrfw-triggerRow.mrfw-railRow {\n width: 36px;\n margin: 8px 0 10px;\n}\n\n.mrfw-trigger {\n box-sizing: border-box;\n flex: 1;\n min-width: 0;\n display: flex;\n align-items: center;\n gap: 8px;\n width: auto;\n height: 42px;\n margin: 0;\n padding: 0 10px 0 8px;\n border: none;\n border-radius: 12px;\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font-family: inherit;\n font-size: 14px;\n line-height: 22px;\n text-align: left;\n cursor: pointer;\n overflow: hidden;\n transition: background-color var(--ds-transition-duration-fast) var(--ds-ease-in-out);\n}\n\n.mrfw-trigger:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.mrfw-triggerIcon {\n flex: none;\n display: inline-flex;\n}\n\n.mrfw-triggerLabel {\n overflow: hidden;\n white-space: nowrap;\n}\n\n.mrfw-triggerRail {\n flex: none;\n width: 36px;\n height: 36px;\n margin: 0;\n padding: 0;\n gap: 0;\n border-radius: 50%;\n justify-content: center;\n}\n\n/* Icon-only launcher inside the upstream Files tab header. Upstream's\n FilesBody renders .header as one flat flex row \u2014 PathLabel, then the\n reload button, then the files.actions slot occupants \u2014 and each of those\n children carries a stable data-files-* attribute on the flex item itself\n (data-files-root on the pane, data-files-path, data-files-reload).\n A registration order sorts only INSIDE the actions slot and can never\n overtake the reload button, so the launcher is moved with the flex order\n property instead: this wrapper is the slot's root element and therefore a\n direct flex item of .header, and order: -1 puts it ahead of every\n order-0 sibling while the :has() rule below keeps the path first. */\n.mrfw-fileLauncher {\n order: -1;\n flex: none;\n display: flex;\n align-items: center;\n}\n\n/* Pin the upstream path label ahead of the launcher \u2014 but only while this\n plugin is in the row: the :has() guard keeps a rule that names an\n upstream element inert whenever the launcher is absent. The\n :not([data-files-entry]) qualifier is load-bearing, not decorative:\n upstream puts data-files-path on EVERY tree row too (the row's own\n absolute path), so an unqualified selector would match the whole tree,\n not just the header's PathLabel. Both selectors depend on upstream's DOM\n shape, which is not part of the slot contract; if it changes, the rules\n simply stop matching and the launcher falls back to its in-slot position \u2014\n a silent degradation, never a broken header. */\n[data-files-root]:has(.mrfw-fileLauncher) [data-files-path]:not([data-files-entry]) {\n order: -2;\n}\n\n/* The root switcher is the launcher's grown-up form on runtimes whose\n files.actions owner carries absolutePath. It takes the path label's flex\n position and stretch: order: -1 ahead of every order-0 sibling, and a\n growing min-width:0 item so both the trigger label and the PathLabel\n inside it can ellipsize instead of pushing the header controls out. */\n.mrfw-rootSwitcher {\n order: -1;\n flex: 1 1 auto;\n min-width: 0;\n display: flex;\n align-items: center;\n /* Same 4px the upstream .header puts between its own flex items, so the\n takeover controls (reload, open-in-app fork) sit at the same offsets the\n hidden native ones did \u2014 no drift between primary and additional state. */\n gap: 4px;\n}\n\n/* While the switcher exists it hides the upstream path label \u2014 the\n :has() guard again keeps the upstream-naming rule inert unless this\n plugin is actually in the row. The switcher draws its own PathLabel of\n the selected root next to the trigger (className mrfw-switcherPath, no\n data-files-* attributes), so the header keeps a path in the same spot.\n :not([data-files-entry]) is what confines this rule to the header:\n every tree row carries data-files-path with its own path as the value,\n and only the PathLabel carries it bare. Without the qualifier this\n display:none would blank the entire native tree. */\n[data-files-root]:has(.mrfw-rootSwitcher) [data-files-path]:not([data-files-entry]) {\n display: none;\n}\n\n/* While the switcher names an additional root, the native tree body and its\n reload hide: the reload refreshes the very tree that is no longer\n visible, and the plugin's forked tree is portaled in as the body's sibling.\n The header still offers a reload \u2014 the switcher's own .mrfw-switcherReload\n calls the forked face's refresh. The native tree keeps watching in the\n background, so switching back to the primary root restores it instantly. */\n[data-files-root]:has(.mrfw-rootSwitcher[data-root-kind=\"additional\"]) [data-files-body],\n[data-files-root]:has(.mrfw-rootSwitcher[data-root-kind=\"additional\"]) [data-files-reload] {\n display: none;\n}\n\n/* While the switcher names an additional root, the native open-in-app split\n button hides too: its slot props carry the pane's primary root, so it\n would always open the primary root no matter what the switcher selected.\n The header gets the plugin's own fork of that same control instead (the\n open-in-app directory), wired to the selected root.\n\n The slot's render site is wrapped in the renderer's own anchor \u2014\n <div data-slot=\"sidebar.right.tab.files.actions\" style=\"display:contents\">\n \u2014 so the header's flex items inside it are the occupants themselves, and\n the native item is the ui-primitives Menu anchor span wrapping the marked\n .split. The first selector hides that occupant item through the data-slot\n anchor: hiding only the inner control would leave the empty anchor span\n as a zero-width flex item whose header gap pushes the fork 4px left of\n the native position. The wrapper itself can never be the target \u2014 its\n inline display:contents beats any stylesheet rule, and hiding it would\n take the switcher with it. The second selector is the fallback for an\n upstream that moves the attribute off the item. The fork survives both:\n the only data-open-target inside .mrfw-rootSwitcher is its own\n .mrfw-openSplit, which :not() excludes. */\n[data-files-root]:has(.mrfw-rootSwitcher[data-root-kind=\"additional\"]) [data-slot=\"sidebar.right.tab.files.actions\"] > *:has([data-open-target=\"directory\"]:not(.mrfw-openSplit)),\n[data-files-root]:has(.mrfw-rootSwitcher[data-root-kind=\"additional\"]) [data-open-target=\"directory\"]:not(.mrfw-openSplit) {\n display: none;\n}\n\n/* The trigger borrows the path label's look: quiet text with a folder\n glyph, the hover of the neighboring header buttons, and the overflow\n behavior PathLabel itself uses in a narrow header. It may shrink but\n never grows \u2014 the PathLabel after it owns the free header space. */\n.mrfw-rootSwitchBtn {\n flex: 0 1 auto;\n min-width: 0;\n display: inline-flex;\n align-items: center;\n gap: 4px;\n height: 28px;\n margin: 0;\n padding: 0 4px;\n border: none;\n border-radius: 8px;\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font-family: inherit;\n font-size: inherit;\n line-height: 20px;\n text-align: left;\n cursor: pointer;\n transition: background-color var(--ds-transition-duration-fast) var(--ds-ease-in-out);\n}\n\n.mrfw-rootSwitchBtn:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.mrfw-rootSwitchBtn .mrfw-triggerIcon {\n color: var(--dsw-alias-label-secondary);\n}\n\n.mrfw-rootSwitchLabel {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mrfw-rootSwitchChevron {\n margin-left: auto;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* The selected root's full path sits between the trigger and the reload \u2014\n the very PathLabel upstream draws there, so it brings its own grow,\n left-clip and hover-title behavior; only the gutters are ours. The right\n margin plus the container gap reproduces upstream's .path margin-right\n 12px + header gap 4px; the left gutter comes from the same container gap. */\n.mrfw-switcherPath {\n margin: 0 12px 0 0;\n}\n\n/* The takeover's own reload, standing exactly where the hidden native one\n did: the upstream .tool recipe verbatim (28px hit area, 15px glyph,\n secondary voice, hover fills). It calls the forked face's refresh \u2014 an\n in-place re-list, not a remount. */\n.mrfw-switcherReload {\n flex: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 6px;\n border: none;\n border-radius: var(--dsw-radius-sm);\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n line-height: 1;\n cursor: pointer;\n}\n\n.mrfw-switcherReload:hover {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n/* \u2500\u2500 files-tree fork (verbatim rules, renamed classes) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Copied from upstream's FilesBody.module.css (ui-sidebar-files @\n 0.2.2-alpha.2) with local names mapped to mrfw-ft* so the rules cannot\n collide with host DOM \u2014 and so the fork's own body (which must NOT carry\n data-files-body) stays visible while the rules above hide the native one.\n The header-only rules (root/header/path/tool/status/statusLine/titleIcon)\n belong to the upstream shell this fork drops and are not mapped. Fork sync\n re-diffs the upstream file and re-applies the rename \u2014 see\n src/client/files-tree/FORK.md. */\n\n/* The pane's own .root already supplies the flex column, color, and font;\n the fork's body takes the same stretch item the hidden native body held,\n including the workspace list's 2px scrollbar offset and stable gutter. */\n.mrfw-ftBody {\n flex: 1 1 auto;\n min-height: 0;\n margin-right: 2px;\n padding: 8px 0 8px 8px;\n overflow: auto;\n scrollbar-gutter: stable;\n}\n\n.mrfw-ftBody::-webkit-scrollbar-track {\n margin: 2px;\n}\n\n.mrfw-ftLevel {\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.mrfw-ftLevel .mrfw-ftLevel {\n padding-left: 18px;\n}\n\n.mrfw-ftItem {\n margin: 0;\n padding: 0;\n}\n\n/* Rows abut, so the hover fill is the whole row; the tree's air is this\n inset, not a gap between rows. */\n.mrfw-ftRow {\n display: flex;\n gap: 6px;\n align-items: center;\n width: 100%;\n min-width: 0;\n padding: 5px 10px;\n color: inherit;\n font: inherit;\n text-align: left;\n background: transparent;\n border: 0;\n border-radius: var(--dsw-radius-md);\n cursor: pointer;\n}\n\n@media (hover: hover) {\n .mrfw-ftRow:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n }\n}\n\n/* The folder glyph rides the tertiary ink, as the workspace tree's does. */\n.mrfw-ftIcon {\n flex: 0 0 auto;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* The type's coloured sheet at the folder glyph's 16px; its colour is its own. */\n.mrfw-ftFileIcon {\n flex: 0 0 auto;\n}\n\n.mrfw-ftName {\n min-width: 0;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n/* Neither a file nor a directory: shown so the directory is reported whole,\n greyed so nobody tries to open it. */\n.mrfw-ftOther {\n color: var(--dsw-alias-label-tertiary);\n cursor: default;\n}\n\n@media (hover: hover) {\n .mrfw-ftOther:hover {\n background: transparent;\n }\n}\n\n.mrfw-ftNote {\n margin: 0;\n padding: 3px 10px;\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n}\n\n/* \u2500\u2500 open-in-app fork (verbatim rules, renamed classes) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Copied from upstream's OpenTargetButton.module.css\n (dsh-client-ui-open-in-app @ 0.2.1-alpha.2) with local names mapped to\n mrfw-open* so the rules cannot collide with host DOM. Fork sync re-diffs\n the upstream file and re-applies the rename \u2014 see\n src/client/open-in-app/FORK.md. */\n\n/* Sized to sit in the document header's 24px control row beside its round\n icon buttons: a hairline pill with a 12px label and a narrow chevron half. */\n.mrfw-openMenuAnchor {\n display: inline-flex;\n flex: none;\n align-self: center;\n}\n\n.mrfw-openSplit {\n display: inline-flex;\n align-items: stretch;\n box-sizing: border-box;\n height: 24px;\n overflow: hidden;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: var(--dsw-radius-sm);\n font-family: var(--dsw-font-family);\n}\n\n.mrfw-openMain,\n.mrfw-openChevron {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: 0;\n background: none;\n color: var(--dsw-alias-label-primary);\n font-size: 11px;\n line-height: 16px;\n white-space: nowrap;\n cursor: pointer;\n}\n\n.mrfw-openMain {\n gap: 4px;\n padding: 3px 5px;\n}\n\n.mrfw-openChevron {\n padding: 3px 4px 3px 3px;\n border-left: 0.5px solid var(--dsw-alias-border-l4);\n color: var(--dsw-alias-label-secondary);\n}\n\n.mrfw-openMain:hover:not(:disabled),\n.mrfw-openMain:focus-visible,\n.mrfw-openChevron:hover:not(:disabled),\n.mrfw-openChevron:focus-visible {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n/* Pending disables clicks without a restyle; only hover feedback pauses. */\n.mrfw-openMain:disabled,\n.mrfw-openChevron:disabled {\n cursor: default;\n}\n\n.mrfw-openAppIcon {\n object-fit: contain;\n}\n\n/* The unavailable preview emphasizes the same actions with a labeled button. */\n.mrfw-openSplit[data-size='large'] {\n height: 36px;\n border-radius: var(--dsw-radius-md);\n border-color: var(--dsw-alias-border-l2);\n}\n\n.mrfw-openSplit[data-size='large'] .mrfw-openMain {\n gap: 6px;\n padding: 6px 14px;\n font-size: 14px;\n}\n\n.mrfw-openSplit[data-size='large'] .mrfw-openChevron {\n padding: 6px 10px;\n}\n\n.mrfw-openSkeleton { display: inline-block; flex: none; border-radius: var(--dsw-radius-xs); background: var(--dsw-alias-interactive-bg-hover); }\n\n.mrfw-overlay {\n position: fixed;\n inset: 0;\n z-index: 1000;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 24px;\n}\n\n.mrfw-mask {\n position: absolute;\n inset: 0;\n background: var(--dsw-alias-bg-mask-1);\n backdrop-filter: var(--dsw-mask-blur);\n}\n\n.mrfw-dialog {\n position: relative;\n z-index: 1;\n display: flex;\n flex-direction: column;\n width: min(640px, 100%);\n max-height: 100%;\n overflow: hidden;\n border: none;\n border-radius: 24px;\n background: var(--dsw-alias-bg-layer-2);\n box-shadow: var(--dsw-elevation-prominent);\n color: var(--dsw-alias-label-primary);\n font-family: inherit;\n outline: none;\n}\n\n.mrfw-header {\n flex: none;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 22px 14px 12px 24px;\n}\n\n.mrfw-title {\n margin: 0;\n font-size: 16px;\n line-height: 24px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n.mrfw-close {\n flex: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border: none;\n border-radius: 8px;\n background: transparent;\n cursor: pointer;\n color: var(--dsw-alias-label-secondary);\n transition: background-color var(--ds-transition-duration-fast) var(--ds-ease-in-out);\n}\n\n.mrfw-close:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.mrfw-body {\n display: flex;\n flex-direction: column;\n min-height: 0;\n overflow-y: auto;\n padding: 0 24px 24px;\n}\n\n.mrfw-alert {\n margin: 0 0 8px;\n font-size: 14px;\n line-height: 22px;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.mrfw-section {\n display: flex;\n flex-direction: column;\n}\n\n.mrfw-section + .mrfw-section {\n margin-top: 8px;\n}\n\n.mrfw-sectionTitleRow {\n display: flex;\n align-items: baseline;\n gap: 8px;\n padding: 8px 0 4px;\n}\n\n.mrfw-sectionTitle {\n font-size: 14px;\n line-height: 22px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n.mrfw-sectionNote {\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.mrfw-row {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n padding: 12px 0;\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\n}\n\n.mrfw-rowBare {\n border-bottom: none;\n}\n\n/* A root entry's two lines: the display name on top, the absolute path under\n it in the tertiary voice (the workspace rows recipe of the host sidebar). */\n/* Match upstream General Settings' DeveloperToolsRow layout. */\n.mrfw-skillsSettings {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 24px;\n padding: 16px 0;\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\n}\n.mrfw-skillsSettingsText { min-width: 0; }\n.mrfw-skillsSettingsTitle { font-size: 14px; line-height: 20px; }\n.mrfw-skillsSettingsDescription {\n margin-top: 4px;\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n line-height: 18px;\n}\n.mrfw-rootText {\n display: flex;\n flex: 1 1 240px;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n\n/* Keep status tags and permission glyphs visible beside the alias, wrapping\n onto another line when the dialog is narrow. */\n.mrfw-rootName {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 6px;\n min-width: 0;\n font-size: 14px;\n line-height: 20px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n.mrfw-rootNameText {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mrfw-rootPath {\n font-family: var(--ds-font-family-code);\n font-size: 12px;\n line-height: 18px;\n word-break: break-all;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* The path doubles as the copy control: an unstyled button that keeps the\n text recipe and makes the whole string clickable \u2014 the whole region, not\n just the icon, copies on click. */\n.mrfw-pathCopy {\n display: block;\n width: 100%;\n padding: 0;\n border: 0;\n background: transparent;\n text-align: left;\n cursor: pointer;\n}\n\n/* The trailing icon stays in flow but invisible until the path is hovered\n or focused: appearing never re-wraps the text, and the icon inherits the\n path's own tertiary ink. */\n.mrfw-pathCopyIcon {\n display: inline-flex;\n margin-left: 4px;\n vertical-align: -2px;\n opacity: 0;\n transition: opacity var(--ds-transition-duration-fast) var(--ds-ease-in-out);\n}\n\n.mrfw-pathCopy:hover .mrfw-pathCopyIcon,\n.mrfw-pathCopy:focus-visible .mrfw-pathCopyIcon {\n opacity: 1;\n}\n\n.mrfw-note {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.mrfw-stateWarn {\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.mrfw-btn {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n gap: 4px;\n height: 28px;\n padding: 0 10px;\n border: none;\n border-radius: 14px;\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font-family: inherit;\n font-size: 12px;\n line-height: 18px;\n cursor: pointer;\n transition: background-color var(--ds-transition-duration-fast) var(--ds-ease-in-out);\n}\n\n.mrfw-btn[aria-busy='true'] {\n cursor: wait;\n}\n\n.mrfw-btn:disabled {\n cursor: not-allowed;\n opacity: 0.4;\n}\n\n.mrfw-btnGhost:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.mrfw-btnGhost:active:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-active);\n}\n\n/* The solid destructive action of the capsule family: the upgrade confirm's\n red button. state-error-primary is the error ink upstream also uses as a\n fill (StateDot); the hover mixes in the host's primary label token to\n follow its theme and contrast choices. Neither color is a guessed token:\n an undefined var() would fall back\n to transparent and white-out the whole button. */\n.mrfw-btnDanger {\n background: var(--dsw-alias-state-error-primary);\n color: var(--dsw-alias-label-primary-foreground);\n}\n\n.mrfw-btnDanger:hover:not(:disabled) {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 85%, var(--dsw-alias-label-primary));\n}\n\n.mrfw-btnOutline {\n border: 0.5px solid var(--dsw-alias-border-l3);\n background: transparent;\n}\n\n.mrfw-btnOutline:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.mrfw-btnPrimary {\n background: var(--dsw-alias-button-primary-fill);\n color: var(--dsw-alias-label-primary-foreground);\n}\n\n.mrfw-btnPrimary:hover:not(:disabled) {\n background: var(--dsw-alias-button-primary-hover);\n}\n\n/* One icon-only action: a square of the capsule family, meaning in title.\n Declared after the .mrfw-btn family so its padding wins the cascade tie:\n the capsule's 0 10px padding would otherwise squeeze the icon glyph. */\n.mrfw-iconBtn {\n width: 28px;\n padding: 0;\n border-radius: 8px;\n}\n\n.mrfw-input {\n flex: 1 1 220px;\n min-width: 0;\n height: 32px;\n padding: 0 8px;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n font-family: inherit;\n font-size: 14px;\n line-height: 22px;\n outline: none;\n transition: border-color var(--ds-transition-duration-fast) var(--ds-ease-in-out);\n}\n\n.mrfw-input:focus {\n border-color: var(--dsw-alias-brand-primary);\n}\n\n.mrfw-input::placeholder {\n color: var(--dsw-alias-label-dimmed);\n}\n\n.mrfw-inputInline {\n flex: 1 1 auto;\n}\n\n.mrfw-accessSelect {\n flex: 0 0 auto;\n}\n\n/* The access dropdown's capsule trigger: the Menu anchor wrapper is the flex\n item, the button inside keeps the control row's 32px capsule recipe. */\n.mrfw-accessTrigger {\n padding: 0 12px;\n}\n\n.mrfw-cwdTag {\n flex: none;\n padding: 0 6px;\n border-radius: 4px;\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n font-size: 11px;\n line-height: 18px;\n}\n\n.mrfw-accessBadge {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n}\n\n.mrfw-accessBadge {\n flex: none;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.mrfw-aliasEditor {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n flex-basis: 100%;\n}\n\n.mrfw-actions {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n margin-top: 16px;\n padding-top: 16px;\n border-top: 0.5px solid var(--dsw-alias-border-l2);\n}\n\n/* One control row, one height: the capsule buttons match the 32px input. */\n.mrfw-actions .mrfw-btn {\n height: 32px;\n border-radius: 16px;\n}\n\n.mrfw-actions .mrfw-iconBtn {\n width: 32px;\n}\n";
14
+ export declare const STYLES = "\n/* Sidebar footer row, mirroring the host ui-settings-general trigger: the row\n * wrapper overhangs each side by 2px (width calc(100% + 4px) against the\n * shell's 12px inline padding) so the icon's ink line lands at 18px from the\n * column edge \u2014 exactly where the Settings gear below sits. */\n.mrfw-triggerRow {\n flex: none;\n display: flex;\n align-items: center;\n gap: 8px;\n width: calc(100% + 4px);\n margin: 4px -2px;\n}\n\n.mrfw-triggerRow.mrfw-railRow {\n width: 36px;\n margin: 8px 0 10px;\n}\n\n.mrfw-trigger {\n box-sizing: border-box;\n flex: 1;\n min-width: 0;\n display: flex;\n align-items: center;\n gap: 8px;\n width: auto;\n height: 42px;\n margin: 0;\n padding: 0 10px 0 8px;\n border: none;\n border-radius: 12px;\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font-family: inherit;\n font-size: 14px;\n line-height: 22px;\n text-align: left;\n cursor: pointer;\n overflow: hidden;\n transition: background-color var(--ds-transition-duration-fast) var(--ds-ease-in-out);\n}\n\n.mrfw-trigger:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.mrfw-triggerIcon {\n flex: none;\n display: inline-flex;\n}\n\n.mrfw-triggerLabel {\n overflow: hidden;\n white-space: nowrap;\n}\n\n.mrfw-triggerRail {\n flex: none;\n width: 36px;\n height: 36px;\n margin: 0;\n padding: 0;\n gap: 0;\n border-radius: 50%;\n justify-content: center;\n}\n\n/* Icon-only launcher inside the upstream Files tab header. Upstream's\n FilesBody renders .header as one flat flex row \u2014 PathLabel, then the\n reload button, then the files.actions slot occupants \u2014 and each of those\n children carries a stable data-files-* attribute on the flex item itself\n (data-files-root on the pane, data-files-path, data-files-reload).\n A registration order sorts only INSIDE the actions slot and can never\n overtake the reload button, so the launcher is moved with the flex order\n property instead: this wrapper is the slot's root element and therefore a\n direct flex item of .header, and order: -1 puts it ahead of every\n order-0 sibling while the :has() rule below keeps the path first. */\n.mrfw-fileLauncher {\n order: -1;\n flex: none;\n display: flex;\n align-items: center;\n}\n\n/* Pin the upstream path label ahead of the launcher \u2014 but only while this\n plugin is in the row: the :has() guard keeps a rule that names an\n upstream element inert whenever the launcher is absent. The\n :not([data-files-entry]) qualifier is load-bearing, not decorative:\n upstream puts data-files-path on EVERY tree row too (the row's own\n absolute path), so an unqualified selector would match the whole tree,\n not just the header's PathLabel. Both selectors depend on upstream's DOM\n shape, which is not part of the slot contract; if it changes, the rules\n simply stop matching and the launcher falls back to its in-slot position \u2014\n a silent degradation, never a broken header. */\n[data-files-root]:has(.mrfw-fileLauncher) [data-files-path]:not([data-files-entry]) {\n order: -2;\n}\n\n/* The root switcher is the launcher's grown-up form on runtimes whose\n files.actions owner carries absolutePath. It takes the path label's flex\n position and stretch: order: -1 ahead of every order-0 sibling, and a\n growing min-width:0 item so both the trigger label and the PathLabel\n inside it can ellipsize instead of pushing the header controls out. */\n.mrfw-rootSwitcher {\n order: -1;\n flex: 1 1 auto;\n min-width: 0;\n display: flex;\n align-items: center;\n /* Same 4px the upstream .header puts between its own flex items, so the\n takeover controls (reload, open-in-app fork) sit at the same offsets the\n hidden native ones did \u2014 no drift between primary and additional state. */\n gap: 4px;\n}\n\n/* While the switcher exists it hides the upstream path label \u2014 the\n :has() guard again keeps the upstream-naming rule inert unless this\n plugin is actually in the row. The switcher draws its own PathLabel of\n the selected root next to the trigger (className mrfw-switcherPath, no\n data-files-* attributes), so the header keeps a path in the same spot.\n :not([data-files-entry]) is what confines this rule to the header:\n every tree row carries data-files-path with its own path as the value,\n and only the PathLabel carries it bare. Without the qualifier this\n display:none would blank the entire native tree. */\n[data-files-root]:has(.mrfw-rootSwitcher) [data-files-path]:not([data-files-entry]) {\n display: none;\n}\n\n/* While the switcher names an additional root, the native tree body and its\n reload hide: the reload refreshes the very tree that is no longer\n visible, and the plugin's forked tree is portaled in as the body's sibling.\n The header still offers a reload \u2014 the switcher's own .mrfw-switcherReload\n calls the forked face's refresh. The native tree keeps watching in the\n background, so switching back to the primary root restores it instantly. */\n[data-files-root]:has(.mrfw-rootSwitcher[data-root-kind=\"additional\"]) [data-files-body],\n[data-files-root]:has(.mrfw-rootSwitcher[data-root-kind=\"additional\"]) [data-files-reload] {\n display: none;\n}\n\n/* Newer Files pages place the navigator beside the header instead of\n wrapping both in data-files-root. Target the page without hiding the\n portaled replacement or any of its rows. */\n[data-files-page]:has(.mrfw-fileLauncher) [data-files-path]:not([data-files-entry]) { order: -2; }\n[data-files-page]:has(.mrfw-rootSwitcher) [data-files-path]:not([data-files-entry]) { display: none; }\n[data-files-page]:has(.mrfw-rootSwitcher[data-root-kind=\"additional\"]) [data-files-content],\n[data-files-page]:has(.mrfw-rootSwitcher[data-root-kind=\"additional\"]) [data-files-reload] { display: none; }\n[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)),\n[data-files-page]:has(.mrfw-rootSwitcher[data-root-kind=\"additional\"]) [data-open-target=\"directory\"]:not(.mrfw-openSplit) { display: none; }\n\n/* While the switcher names an additional root, the native open-in-app split\n button hides too: its slot props carry the pane's primary root, so it\n would always open the primary root no matter what the switcher selected.\n The header gets the plugin's own fork of that same control instead (the\n open-in-app directory), wired to the selected root.\n\n The slot's render site is wrapped in the renderer's own anchor \u2014\n <div data-slot=\"sidebar.right.tab.files.actions\" style=\"display:contents\">\n \u2014 so the header's flex items inside it are the occupants themselves, and\n the native item is the ui-primitives Menu anchor span wrapping the marked\n .split. The first selector hides that occupant item through the data-slot\n anchor: hiding only the inner control would leave the empty anchor span\n as a zero-width flex item whose header gap pushes the fork 4px left of\n the native position. The wrapper itself can never be the target \u2014 its\n inline display:contents beats any stylesheet rule, and hiding it would\n take the switcher with it. The second selector is the fallback for an\n upstream that moves the attribute off the item. The fork survives both:\n the only data-open-target inside .mrfw-rootSwitcher is its own\n .mrfw-openSplit, which :not() excludes. */\n[data-files-root]:has(.mrfw-rootSwitcher[data-root-kind=\"additional\"]) [data-slot=\"sidebar.right.tab.files.actions\"] > *:has([data-open-target=\"directory\"]:not(.mrfw-openSplit)),\n[data-files-root]:has(.mrfw-rootSwitcher[data-root-kind=\"additional\"]) [data-open-target=\"directory\"]:not(.mrfw-openSplit) {\n display: none;\n}\n\n/* The trigger borrows the path label's look: quiet text with a folder\n glyph, the hover of the neighboring header buttons, and the overflow\n behavior PathLabel itself uses in a narrow header. It may shrink but\n never grows \u2014 the PathLabel after it owns the free header space. */\n.mrfw-rootSwitchBtn {\n flex: 0 1 auto;\n min-width: 0;\n display: inline-flex;\n align-items: center;\n gap: 4px;\n height: 28px;\n margin: 0;\n padding: 0 4px;\n border: none;\n border-radius: 8px;\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font-family: inherit;\n font-size: inherit;\n line-height: 20px;\n text-align: left;\n cursor: pointer;\n transition: background-color var(--ds-transition-duration-fast) var(--ds-ease-in-out);\n}\n\n.mrfw-rootSwitchBtn:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.mrfw-rootSwitchBtn .mrfw-triggerIcon {\n color: var(--dsw-alias-label-secondary);\n}\n\n.mrfw-rootSwitchLabel {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mrfw-rootSwitchChevron {\n margin-left: auto;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* The selected root's full path sits between the trigger and the reload \u2014\n the very PathLabel upstream draws there, so it brings its own grow,\n left-clip and hover-title behavior; only the gutters are ours. The right\n margin plus the container gap reproduces upstream's .path margin-right\n 12px + header gap 4px; the left gutter comes from the same container gap. */\n.mrfw-switcherPath {\n margin: 0 12px 0 0;\n}\n\n/* The takeover's own reload, standing exactly where the hidden native one\n did: the upstream .tool recipe verbatim (28px hit area, 15px glyph,\n secondary voice, hover fills). It calls the forked face's refresh \u2014 an\n in-place re-list, not a remount. */\n.mrfw-switcherReload {\n flex: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 6px;\n border: none;\n border-radius: var(--dsw-radius-sm);\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n line-height: 1;\n cursor: pointer;\n}\n\n.mrfw-switcherReload:hover {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n/* \u2500\u2500 files-tree fork (verbatim rules, renamed classes) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Copied from upstream's FilesBody.module.css (ui-sidebar-files @\n 0.2.2-alpha.2) with local names mapped to mrfw-ft* so the rules cannot\n collide with host DOM \u2014 and so the fork's own body (which must NOT carry\n data-files-body) stays visible while the rules above hide the native one.\n The header-only rules (root/header/path/tool/status/statusLine/titleIcon)\n belong to the upstream shell this fork drops and are not mapped. Fork sync\n re-diffs the upstream file and re-applies the rename \u2014 see\n src/client/files-tree/FORK.md. */\n\n/* The pane's own .root already supplies the flex column, color, and font;\n the fork's body takes the same stretch item the hidden native body held,\n including the workspace list's 2px scrollbar offset and stable gutter. */\n.mrfw-ftBody {\n flex: 1 1 auto;\n min-height: 0;\n margin-right: 2px;\n padding: 8px 0 8px 8px;\n overflow: auto;\n scrollbar-gutter: stable;\n}\n\n.mrfw-ftBody::-webkit-scrollbar-track {\n margin: 2px;\n}\n\n.mrfw-ftLevel {\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.mrfw-ftLevel .mrfw-ftLevel {\n padding-left: 18px;\n}\n\n.mrfw-ftItem {\n margin: 0;\n padding: 0;\n}\n\n/* Rows abut, so the hover fill is the whole row; the tree's air is this\n inset, not a gap between rows. */\n.mrfw-ftRow {\n display: flex;\n gap: 6px;\n align-items: center;\n width: 100%;\n min-width: 0;\n padding: 5px 10px;\n color: inherit;\n font: inherit;\n text-align: left;\n background: transparent;\n border: 0;\n border-radius: var(--dsw-radius-md);\n cursor: pointer;\n}\n\n@media (hover: hover) {\n .mrfw-ftRow:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n }\n}\n\n/* The folder glyph rides the tertiary ink, as the workspace tree's does. */\n.mrfw-ftIcon {\n flex: 0 0 auto;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* The type's coloured sheet at the folder glyph's 16px; its colour is its own. */\n.mrfw-ftFileIcon {\n flex: 0 0 auto;\n}\n\n.mrfw-ftName {\n min-width: 0;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n/* Neither a file nor a directory: shown so the directory is reported whole,\n greyed so nobody tries to open it. */\n.mrfw-ftOther {\n color: var(--dsw-alias-label-tertiary);\n cursor: default;\n}\n\n@media (hover: hover) {\n .mrfw-ftOther:hover {\n background: transparent;\n }\n}\n\n.mrfw-ftNote {\n margin: 0;\n padding: 3px 10px;\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n}\n\n/* \u2500\u2500 open-in-app fork (verbatim rules, renamed classes) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Copied from upstream's OpenTargetButton.module.css\n (dsh-client-ui-open-in-app @ 0.2.1-alpha.2) with local names mapped to\n mrfw-open* so the rules cannot collide with host DOM. Fork sync re-diffs\n the upstream file and re-applies the rename \u2014 see\n src/client/open-in-app/FORK.md. */\n\n/* Sized to sit in the document header's 24px control row beside its round\n icon buttons: a hairline pill with a 12px label and a narrow chevron half. */\n.mrfw-openMenuAnchor {\n display: inline-flex;\n flex: none;\n align-self: center;\n}\n\n.mrfw-openSplit {\n display: inline-flex;\n align-items: stretch;\n box-sizing: border-box;\n height: 24px;\n overflow: hidden;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: var(--dsw-radius-sm);\n font-family: var(--dsw-font-family);\n}\n\n.mrfw-openMain,\n.mrfw-openChevron {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: 0;\n background: none;\n color: var(--dsw-alias-label-primary);\n font-size: 11px;\n line-height: 16px;\n white-space: nowrap;\n cursor: pointer;\n}\n\n.mrfw-openMain {\n gap: 4px;\n padding: 3px 5px;\n}\n\n.mrfw-openChevron {\n padding: 3px 4px 3px 3px;\n border-left: 0.5px solid var(--dsw-alias-border-l4);\n color: var(--dsw-alias-label-secondary);\n}\n\n.mrfw-openMain:hover:not(:disabled),\n.mrfw-openMain:focus-visible,\n.mrfw-openChevron:hover:not(:disabled),\n.mrfw-openChevron:focus-visible {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n/* Pending disables clicks without a restyle; only hover feedback pauses. */\n.mrfw-openMain:disabled,\n.mrfw-openChevron:disabled {\n cursor: default;\n}\n\n.mrfw-openAppIcon {\n object-fit: contain;\n}\n\n/* The unavailable preview emphasizes the same actions with a labeled button. */\n.mrfw-openSplit[data-size='large'] {\n height: 36px;\n border-radius: var(--dsw-radius-md);\n border-color: var(--dsw-alias-border-l2);\n}\n\n.mrfw-openSplit[data-size='large'] .mrfw-openMain {\n gap: 6px;\n padding: 6px 14px;\n font-size: 14px;\n}\n\n.mrfw-openSplit[data-size='large'] .mrfw-openChevron {\n padding: 6px 10px;\n}\n\n.mrfw-openSkeleton { display: inline-block; flex: none; border-radius: var(--dsw-radius-xs); background: var(--dsw-alias-interactive-bg-hover); }\n\n.mrfw-overlay {\n position: fixed;\n inset: 0;\n z-index: 1000;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 24px;\n}\n\n.mrfw-mask {\n position: absolute;\n inset: 0;\n background: var(--dsw-alias-bg-mask-1);\n backdrop-filter: var(--dsw-mask-blur);\n}\n\n.mrfw-dialog {\n position: relative;\n z-index: 1;\n display: flex;\n flex-direction: column;\n width: min(640px, 100%);\n max-height: 100%;\n overflow: hidden;\n border: none;\n border-radius: 24px;\n background: var(--dsw-alias-bg-layer-2);\n box-shadow: var(--dsw-elevation-prominent);\n color: var(--dsw-alias-label-primary);\n font-family: inherit;\n outline: none;\n}\n\n.mrfw-header {\n flex: none;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 22px 14px 12px 24px;\n}\n\n.mrfw-title {\n margin: 0;\n font-size: 16px;\n line-height: 24px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n.mrfw-close {\n flex: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border: none;\n border-radius: 8px;\n background: transparent;\n cursor: pointer;\n color: var(--dsw-alias-label-secondary);\n transition: background-color var(--ds-transition-duration-fast) var(--ds-ease-in-out);\n}\n\n.mrfw-close:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.mrfw-body {\n display: flex;\n flex-direction: column;\n min-height: 0;\n overflow-y: auto;\n padding: 0 24px 24px;\n}\n\n.mrfw-alert {\n margin: 0 0 8px;\n font-size: 14px;\n line-height: 22px;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.mrfw-section {\n display: flex;\n flex-direction: column;\n}\n\n.mrfw-section + .mrfw-section {\n margin-top: 8px;\n}\n\n.mrfw-sectionTitleRow {\n display: flex;\n align-items: baseline;\n gap: 8px;\n padding: 8px 0 4px;\n}\n\n.mrfw-sectionTitle {\n font-size: 14px;\n line-height: 22px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n.mrfw-sectionNote {\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.mrfw-row {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n padding: 12px 0;\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\n}\n\n.mrfw-rowBare {\n border-bottom: none;\n}\n\n/* A root entry's two lines: the display name on top, the absolute path under\n it in the tertiary voice (the workspace rows recipe of the host sidebar). */\n/* Match upstream General Settings' DeveloperToolsRow layout. */\n.mrfw-skillsSettings {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 24px;\n padding: 16px 0;\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\n}\n.mrfw-skillsSettingsText { min-width: 0; }\n.mrfw-settingsTitleRow {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.mrfw-skillsSettingsTitle { font-size: 14px; line-height: 20px; }\n.mrfw-skillsSettingsDescription {\n margin-top: 4px;\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n line-height: 18px;\n}\n.mrfw-rootText {\n display: flex;\n flex: 1 1 240px;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n\n/* Keep status tags and permission glyphs visible beside the alias, wrapping\n onto another line when the dialog is narrow. */\n.mrfw-rootName {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 6px;\n min-width: 0;\n font-size: 14px;\n line-height: 20px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n.mrfw-rootNameText {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mrfw-rootPath {\n font-family: var(--ds-font-family-code);\n font-size: 12px;\n line-height: 18px;\n word-break: break-all;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* The path doubles as the copy control: an unstyled button that keeps the\n text recipe and makes the whole string clickable \u2014 the whole region, not\n just the icon, copies on click. */\n.mrfw-pathCopy {\n display: block;\n width: 100%;\n padding: 0;\n border: 0;\n background: transparent;\n text-align: left;\n cursor: pointer;\n}\n\n/* The trailing icon stays in flow but invisible until the path is hovered\n or focused: appearing never re-wraps the text, and the icon inherits the\n path's own tertiary ink. */\n.mrfw-pathCopyIcon {\n display: inline-flex;\n margin-left: 4px;\n vertical-align: -2px;\n opacity: 0;\n transition: opacity var(--ds-transition-duration-fast) var(--ds-ease-in-out);\n}\n\n.mrfw-pathCopy:hover .mrfw-pathCopyIcon,\n.mrfw-pathCopy:focus-visible .mrfw-pathCopyIcon {\n opacity: 1;\n}\n\n.mrfw-note {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.mrfw-stateWarn {\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.mrfw-btn {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n gap: 4px;\n height: 28px;\n padding: 0 10px;\n border: none;\n border-radius: 14px;\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font-family: inherit;\n font-size: 12px;\n line-height: 18px;\n cursor: pointer;\n transition: background-color var(--ds-transition-duration-fast) var(--ds-ease-in-out);\n}\n\n.mrfw-btn[aria-busy='true'] {\n cursor: wait;\n}\n\n.mrfw-btn:disabled {\n cursor: not-allowed;\n opacity: 0.4;\n}\n\n.mrfw-btnGhost:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.mrfw-btnGhost:active:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-active);\n}\n\n/* The solid destructive action of the capsule family: the upgrade confirm's\n red button. state-error-primary is the error ink upstream also uses as a\n fill (StateDot); the hover mixes in the host's primary label token to\n follow its theme and contrast choices. Neither color is a guessed token:\n an undefined var() would fall back\n to transparent and white-out the whole button. */\n.mrfw-btnDanger {\n background: var(--dsw-alias-state-error-primary);\n color: var(--dsw-alias-label-primary-foreground);\n}\n\n.mrfw-btnDanger:hover:not(:disabled) {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 85%, var(--dsw-alias-label-primary));\n}\n\n.mrfw-btnOutline {\n border: 0.5px solid var(--dsw-alias-border-l3);\n background: transparent;\n}\n\n.mrfw-btnOutline:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.mrfw-btnPrimary {\n background: var(--dsw-alias-button-primary-fill);\n color: var(--dsw-alias-label-primary-foreground);\n}\n\n.mrfw-btnPrimary:hover:not(:disabled) {\n background: var(--dsw-alias-button-primary-hover);\n}\n\n/* One icon-only action: a square of the capsule family, meaning in title.\n Declared after the .mrfw-btn family so its padding wins the cascade tie:\n the capsule's 0 10px padding would otherwise squeeze the icon glyph. */\n.mrfw-iconBtn {\n width: 28px;\n padding: 0;\n border-radius: 8px;\n}\n\n.mrfw-input {\n flex: 1 1 220px;\n min-width: 0;\n height: 32px;\n padding: 0 8px;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n font-family: inherit;\n font-size: 14px;\n line-height: 22px;\n outline: none;\n transition: border-color var(--ds-transition-duration-fast) var(--ds-ease-in-out);\n}\n\n.mrfw-input:focus {\n border-color: var(--dsw-alias-brand-primary);\n}\n\n.mrfw-input::placeholder {\n color: var(--dsw-alias-label-dimmed);\n}\n\n.mrfw-inputInline {\n flex: 1 1 auto;\n}\n\n.mrfw-accessSelect {\n flex: 0 0 auto;\n}\n\n/* The access dropdown's capsule trigger: the Menu anchor wrapper is the flex\n item, the button inside keeps the control row's 32px capsule recipe. */\n.mrfw-accessTrigger {\n padding: 0 12px;\n}\n\n.mrfw-cwdTag {\n flex: none;\n padding: 0 6px;\n border-radius: 4px;\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n font-size: 11px;\n line-height: 18px;\n}\n\n.mrfw-accessBadge {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n}\n\n.mrfw-accessBadge {\n flex: none;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.mrfw-aliasEditor {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n flex-basis: 100%;\n}\n\n.mrfw-actions {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n margin-top: 16px;\n padding-top: 16px;\n border-top: 0.5px solid var(--dsw-alias-border-l2);\n}\n\n/* One control row, one height: the capsule buttons match the 32px input. */\n.mrfw-actions .mrfw-btn {\n height: 32px;\n border-radius: 16px;\n}\n\n.mrfw-actions .mrfw-iconBtn {\n width: 32px;\n}\n";