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

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 (73) hide show
  1. package/CHANGELOG.en.md +32 -1
  2. package/CHANGELOG.md +32 -1
  3. package/README.en.md +25 -8
  4. package/README.md +25 -8
  5. package/cordis.patch.yml +5 -1
  6. package/lib/{llm-message-fYORacvh.js → agent-instructions-BXuKhOzr.js} +1 -84
  7. package/lib/auto-review.js +2 -2
  8. package/lib/client.js +969 -100
  9. package/lib/client.js.map +1 -1
  10. package/lib/{command-Dcz6nULo.js → command-DaVJ-mK3.js} +76 -11
  11. package/lib/command.js +1 -1
  12. package/lib/compat-CTJbpCxu.js +267 -0
  13. package/lib/compat.js +1 -1
  14. package/lib/{dialects-Dr4rDR-H.js → dialects-CdlG3RF9.js} +1 -1
  15. package/lib/{compat-veDqCOoa.js → dsh-version-DCwF7REk.js} +2 -264
  16. package/lib/index.js +10 -8
  17. package/lib/{instructions-KsUwUFE0.js → instructions-Ca80SkZq.js} +696 -27
  18. package/lib/instructions.js +1 -1
  19. package/lib/llm-message-CepyzEBQ.js +84 -0
  20. package/lib/lsp.js +4 -4
  21. package/lib/{metadata-DgqSdVX_.js → metadata-99jgViHQ.js} +1 -1
  22. package/lib/{method-wrapper-CFY3p3j2.js → method-wrapper-D4oUCsEF.js} +22 -2
  23. package/lib/{native-win32-CRNXr11U.js → native-win32-C5oMtt7f.js} +1 -1
  24. package/lib/ops-compatibility.js +58 -0
  25. package/lib/ops-settings-UN2oOKEp.js +40 -0
  26. package/lib/ops-tools-k4JYaOrM.js +152 -0
  27. package/lib/process-fence.js +193 -34
  28. package/lib/{protocol-CnR1KTXy.js → protocol-CwDnFEyK.js} +8 -4
  29. package/lib/{registry-DR_sMuvS.js → registry-BibiukWk.js} +236 -80
  30. package/lib/{registry-lease-win32-DDO-c7ig.js → registry-lease-win32-r4puac9m.js} +1 -1
  31. package/lib/{registry-operation-C2ZV3Jhw.js → registry-operation-CIlmt6_c.js} +5 -1
  32. package/lib/registry.js +2 -2
  33. package/lib/{review-context-NDpKeMCc.js → review-context-OxO-fMWR.js} +24 -7
  34. package/lib/review-context.js +1 -1
  35. package/lib/{roots-Ca_W1S9n.js → roots-CmaWxRVy.js} +112 -22
  36. package/lib/{sandbox-C-vCJCp3.js → sandbox-aobdyQGi.js} +7 -4
  37. package/lib/sandbox.js +1 -1
  38. package/lib/scope.js +162 -12
  39. package/lib/skills-utyuvmwr.js +104 -0
  40. package/lib/skills.js +5 -106
  41. package/lib/{tool-context-CpgIFcCB.js → tool-context-CCeY3mrK.js} +1 -1
  42. package/lib/types/authority/protocol.d.ts +5 -1
  43. package/lib/types/client/files-tree/FilesTree.d.ts +49 -6
  44. package/lib/types/client/files-tree/adapter.d.ts +69 -16
  45. package/lib/types/client/files-tree/face.d.ts +31 -2
  46. package/lib/types/client/files-tree/locate.d.ts +74 -0
  47. package/lib/types/client/files-tree/navigator.d.ts +98 -0
  48. package/lib/types/client/index.d.ts +7 -0
  49. package/lib/types/client/locales.d.ts +30 -0
  50. package/lib/types/client/ops-settings.d.ts +6 -0
  51. package/lib/types/client/skills-settings.d.ts +3 -2
  52. package/lib/types/client/styles.d.ts +1 -1
  53. package/lib/types/command.d.ts +3 -3
  54. package/lib/types/compat/files-tree.d.ts +8 -1
  55. package/lib/types/compat/forge-entrypoints.d.ts +44 -0
  56. package/lib/types/compat/instruction-activity.d.ts +32 -1
  57. package/lib/types/compat/ops-confirmation.d.ts +17 -0
  58. package/lib/types/compat/ops-settings.d.ts +11 -0
  59. package/lib/types/compat/ops-tools.d.ts +25 -0
  60. package/lib/types/compat/session-existence.d.ts +41 -0
  61. package/lib/types/compat/skills-client.d.ts +1 -0
  62. package/lib/types/compat/tool-context.d.ts +4 -0
  63. package/lib/types/compat.d.ts +2 -2
  64. package/lib/types/instructions.d.ts +6 -1
  65. package/lib/types/ops-compatibility.d.ts +34 -0
  66. package/lib/types/registry-operation.d.ts +7 -0
  67. package/lib/types/registry.d.ts +67 -8
  68. package/lib/types/review-context.d.ts +2 -0
  69. package/lib/types/roots.d.ts +116 -20
  70. package/lib/types/scope.d.ts +95 -3
  71. package/lib/workspace-files.js +4 -4
  72. package/lib/{workspace-routing-YPxaQ4V3.js → workspace-routing-D_qwMUh-.js} +4 -4
  73. package/package.json +5 -1
@@ -0,0 +1,98 @@
1
+ /**
2
+ * The document-preview navigator's additional-root retarget.
3
+ *
4
+ * Upstream's `sidebar.files.navigator` Factory (`ui-sidebar-files`, added at
5
+ * `0.2.2-alpha.3`) is the Folder-toggle tree the document preview draws
6
+ * beside its body. Its root is hard-wired to the session's `cwd` — the
7
+ * previewed file's absolute path is located against that root, so a file
8
+ * that lives under an additional root lands outside every tree it can draw:
9
+ * the navigator opens on the primary root, unexpanded and unmarked.
10
+ *
11
+ * The Factory itself cannot be retargeted — it is a registration upstream
12
+ * already owns, and its root is not an input. What CAN move is what its
13
+ * `aside[data-files-navigator]` draws: this occupant mounts in the preview
14
+ * header's `sidebar.right.tab.document.actions` seat (owner prop
15
+ * `absolutePath` — the previewed file's host path), watches the preview's
16
+ * `[data-textpreview-content]` row for the aside, and when the file sits
17
+ * under an available additional root it lends the aside's body area to the
18
+ * forked tree, the same takeover the Files page's root switcher performs.
19
+ * The aside itself — its width, its push/overlay/fill modes, its resize
20
+ * separator — stays native; only the tree inside is the fork's.
21
+ *
22
+ * Everything is keyed by the dockkit PANE id (`data-files-navigator` on the
23
+ * aside, `data-dockkit-pane` on the content section), never by the preview
24
+ * tab: upstream's navigator records live in `store.byPane`, and the pane
25
+ * survives what the tab does not — `replaceTab` mints a fresh tab id, so
26
+ * opening a file from the retargeted tree destroys the preview tab that
27
+ * opened it. The inject-side takeover map holds the pane-record memory
28
+ * (`expanded`/`scrollTop`/`revealed`) across that death; the component's
29
+ * own refs only ever carry the live copy.
30
+ *
31
+ * Three upstream behaviors are reproduced, not approximated:
32
+ *
33
+ * - **Hold lifetime.** The native navigator holds its pane tree only while
34
+ * it is open (`hold(paneId, cwd)` gated on `state.open`): closing the
35
+ * navigator closes the directory node — watches and listings end — while
36
+ * the pane record keeps `expanded`/`scrollTop`/`revealed` for the next
37
+ * open. Here the takeover `signal` is requested when the aside appears
38
+ * and ended when it leaves. The saved snapshot is written into the
39
+ * inject-side map continuously — the bucket's expansion set mirrors into
40
+ * it, the forked body reports its live scroll offset, and a settled
41
+ * reveal records its file — so no cleanup ordering ever decides what is
42
+ * remembered. A dockkit pane has no stable element (a non-empty pane's
43
+ * id rides on its selected tab's host, which `replaceTab` swaps out), so
44
+ * the owner's death is checked by query: the pane lives while some
45
+ * connected element still carries its id, and the watch that drops the
46
+ * memory outlives every `end` because the body's cleanup always wins
47
+ * the unmount race.
48
+ * - **Reveal.** The native navigator seeds `pane.revealing` with the drawn
49
+ * file's address — unless `pane.revealed` already names it — and steps
50
+ * the walk open (ancestors expanded one listing at a time, then the
51
+ * file's row marked current and scrolled into view). The forked tree's
52
+ * `reveal` prop drives the same sequence against the additional root,
53
+ * with the saved `revealed` suppressing a repeat of a completed walk, so
54
+ * collapsing a directory and reopening the navigator keeps the reader's
55
+ * collapse exactly as upstream's pane record does.
56
+ * - **Replace-tab open.** The native navigator's `onOpen` is
57
+ * `tab.actions.openResource(address, { replaceTab: true })` — the opened
58
+ * file takes the preview's own tab. The forked tree's opens route through
59
+ * `openFileIn`, which spells the same intent to `openResourceIn` as
60
+ * `{ replaceTab: tabId }` against the preview's dockkit tab id.
61
+ *
62
+ * Every step degrades silently: no `[data-textpreview-url]` ancestor, no
63
+ * dockkit pane id, no `panel`, an address that is not a session file of
64
+ * THIS session, a file outside every available additional root (the
65
+ * primary root's own files included), or a fork that cannot render — each
66
+ * leaves the native navigator completely untouched. Where the upstream
67
+ * hooks do not exist (a runtime without this navigator), the marker span
68
+ * is the only trace the occupant leaves, and it draws nothing.
69
+ *
70
+ * @module @dsh-electron/dsh-plugin-multi-root-workspace/client/files-tree/navigator
71
+ */
72
+ import { type ReactNode } from 'react';
73
+ import type { NativeTreeRenderer } from '../../compat/files-tree.ts';
74
+ import type { PanelClient } from '../panel-client.ts';
75
+ import { type FilesTreeSurface } from './adapter.tsx';
76
+ /** What the registered document-actions occupant needs beyond the slot owner. */
77
+ export interface NavigatorRetargetProps {
78
+ /** The previewed file's absolute host path — the slot owner's `absolutePath`. */
79
+ readonly absolutePath: string;
80
+ /** This preview's session — the composed session share; absent without one. */
81
+ readonly sessionId: string | undefined;
82
+ /** The channel client; absent when this composition has no host Connection. */
83
+ readonly panel: PanelClient | undefined;
84
+ /**
85
+ * The forked tree's shared props; the registration gates on it, so the
86
+ * occupant only renders where the fork can — the native navigator keeping
87
+ * its tree is the same behavior an upstream Files page without the fork
88
+ * would produce.
89
+ */
90
+ readonly filesTree: FilesTreeSurface;
91
+ readonly nativeTree?: NativeTreeRenderer | undefined;
92
+ }
93
+ /**
94
+ * The marker that lives in the preview header and, while the file it names
95
+ * sits under an additional root, lends the navigator aside to the forked
96
+ * tree. It draws nothing of its own.
97
+ */
98
+ export declare function NavigatorRetarget(props: NavigatorRetargetProps): ReactNode;
@@ -25,6 +25,13 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
25
25
  readonly absolutePath: string;
26
26
  };
27
27
  };
28
+ 'sidebar.right.tab.document.actions': {
29
+ kind: 'list';
30
+ scope: 'session';
31
+ owner: {
32
+ readonly absolutePath: string;
33
+ };
34
+ };
28
35
  }
29
36
  }
30
37
  /** The footer-action cell this plugin owns. */
@@ -21,6 +21,16 @@ export declare const zh: {
21
21
  'autoReview.loadFailed': string;
22
22
  'autoReview.restartRequired': string;
23
23
  'autoReview.settingsFailed': string;
24
+ 'ops.compatibilityEnable': string;
25
+ 'ops.experimental': string;
26
+ 'ops.compatibilityHint': string;
27
+ 'ops.settingsFailed': string;
28
+ 'ops.confirmTitle': string;
29
+ 'ops.confirmDescription': string;
30
+ 'ops.confirmAcknowledge': string;
31
+ 'ops.confirmCancel': string;
32
+ 'ops.confirmClose': string;
33
+ 'ops.confirmEnable': string;
24
34
  'skills.userOnly': string;
25
35
  'panel.skills': string;
26
36
  'panel.skillsEnabled': string;
@@ -65,6 +75,7 @@ export declare const zh: {
65
75
  'panel.copied': string;
66
76
  'panel.reveal': string;
67
77
  'panel.reRegister': string;
78
+ 'panel.redirectedHint': string;
68
79
  'panel.moveUp': string;
69
80
  'panel.moveDown': string;
70
81
  'access.inherit': string;
@@ -100,7 +111,11 @@ export declare const zh: {
100
111
  'error.unavailable': string;
101
112
  'error.copy-failed': string;
102
113
  'error.session-not-found': string;
114
+ 'error.session-not-live': string;
103
115
  'error.unsupported-path': string;
116
+ 'error.protected-path': string;
117
+ 'error.protected-boundary-unknown': string;
118
+ 'error.revive-target-changed': string;
104
119
  'error.fallback': string;
105
120
  };
106
121
  /** The canonical key set. */
@@ -116,6 +131,16 @@ export declare const en: {
116
131
  'autoReview.loadFailed': string;
117
132
  'autoReview.restartRequired': string;
118
133
  'autoReview.settingsFailed': string;
134
+ 'ops.compatibilityEnable': string;
135
+ 'ops.experimental': string;
136
+ 'ops.compatibilityHint': string;
137
+ 'ops.settingsFailed': string;
138
+ 'ops.confirmTitle': string;
139
+ 'ops.confirmDescription': string;
140
+ 'ops.confirmAcknowledge': string;
141
+ 'ops.confirmCancel': string;
142
+ 'ops.confirmClose': string;
143
+ 'ops.confirmEnable': string;
119
144
  'skills.userOnly': string;
120
145
  'panel.skills': string;
121
146
  'panel.skillsEnabled': string;
@@ -160,6 +185,7 @@ export declare const en: {
160
185
  'panel.copied': string;
161
186
  'panel.reveal': string;
162
187
  'panel.reRegister': string;
188
+ 'panel.redirectedHint': string;
163
189
  'panel.moveUp': string;
164
190
  'panel.moveDown': string;
165
191
  'access.inherit': string;
@@ -195,7 +221,11 @@ export declare const en: {
195
221
  'error.unavailable': string;
196
222
  'error.copy-failed': string;
197
223
  'error.session-not-found': string;
224
+ 'error.session-not-live': string;
198
225
  'error.unsupported-path': string;
226
+ 'error.protected-path': string;
227
+ 'error.protected-boundary-unknown': string;
228
+ 'error.revive-target-changed': string;
199
229
  'error.fallback': string;
200
230
  };
201
231
  declare module '@deepseek-ai/dsh-client-ui-slots' {
@@ -0,0 +1,6 @@
1
+ import type { SkillsForm } from '../compat/skills-client.ts';
2
+ import type { Translate } from './WorkspaceFoldersAction.tsx';
3
+ export declare function OpsCompatibilitySettingsCard({ form, t }: {
4
+ form: SkillsForm;
5
+ t: Translate;
6
+ }): import("react").JSX.Element | null;
@@ -10,12 +10,13 @@ export declare function SkillsSettingsCard({ form, t }: CardProps): import("reac
10
10
  /**
11
11
  * The bundle's one `plugins.bundle.config` cell: every plugin-level switch the
12
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
13
+ * Skills and OPS through their settings namespaces, Auto Review through the Plugin
14
14
  * Manager's Loader-row enablement — so one cell never serializes their state.
15
15
  */
16
- export declare function BundleSettingsCard({ form, autoReviewForm, t }: {
16
+ export declare function BundleSettingsCard({ form, autoReviewForm, opsForm, t }: {
17
17
  form?: SkillsForm | undefined;
18
18
  autoReviewForm?: AutoReviewForm | undefined;
19
+ opsForm?: SkillsForm | undefined;
19
20
  t: Translate;
20
21
  }): import("react").JSX.Element;
21
22
  export declare function installSkillsSettingsCard(ctx: Context, t: Translate): void;
@@ -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/* 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";
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/* The document preview's navigator aside, while the retarget holds it: the\n native body hides (it lists the session's primary root regardless of the\n previewed file), and the forked tree \u2014 portaled in as the aside's sibling\n child \u2014 takes the same stretch item in the aside's column flex, right\n down to the body's own padding and 2px scrollbar offset. The aside's\n chrome is untouched: width, push/overlay/fill modes, and the resize\n separator all stay native. The marker attribute is set only while a\n takeover actually holds the aside, so a navigator we never matched \u2014 or\n one whose plugin sheet is gone \u2014 renders exactly as upstream drew it. */\naside[data-mrfw-navigator] > [data-files-body] {\n display: none;\n}\n\n/* The reveal's current row, the fork's spelling of upstream's .current:\n the interactive-active fill, kept under hover exactly as the native\n tree's current row keeps it. */\n.mrfw-ftRow[data-files-current],\n.mrfw-ftRow[data-files-current]:hover {\n background: var(--dsw-alias-interactive-bg-active);\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";
@@ -1,6 +1,6 @@
1
1
  /**
2
- * The user-facing surface: the `/workspace-folders` command and the panel's
3
- * RPC channel.
2
+ * The user-facing surface: the `/workspace-folders` command, its `/add-dirs`
3
+ * alias for adding a root, and the panel's RPC channel.
4
4
  *
5
5
  * They share one module because they are one thing — the operator-facing edge
6
6
  * over `ctx.multiRootRegistry`. The command serves the text surfaces (the
@@ -81,7 +81,7 @@ export declare function apply(ctx: Context, config?: CommandConfig): void;
81
81
  *
82
82
  * @param ctx - the host context that may carry `sessions`.
83
83
  * @param sessionId - the id the panel sent; already required by the wire schema.
84
- * @returns the canonical cwd of that session.
84
+ * @returns the session's cwd spelling, verbatim — the Registry key.
85
85
  * @throws {RootValidationError} `session-not-found` when the service is absent,
86
86
  * the id does not name a live session, or that session has no cwd.
87
87
  */
@@ -27,10 +27,17 @@ export type NativeTreeRenderer = (name: string, input: object, options: {
27
27
  fallback: unknown;
28
28
  }) => unknown;
29
29
  export declare function nativeTreeRenderer(value: unknown): NativeTreeRenderer | undefined;
30
+ /** The revealed file's identity, forwarded as the factory's `current`/`currentRef` inputs. */
31
+ export interface NativeTreeCurrent {
32
+ /** The current row's tree key — the factory marks it `.current` + `aria-current`. */
33
+ readonly path: string;
34
+ /** Optional callback ref for the current row's button — the reveal's scroll target. */
35
+ readonly ref?: ((row: unknown) => void) | undefined;
36
+ }
30
37
  /** The factory consumes absolute, slash-separated keys and already-localized errors. */
31
38
  export declare function nativeTreeInput<F>(state: {
32
39
  readonly root: string;
33
40
  readonly levels: Readonly<Record<string, Level<F>>>;
34
41
  readonly expanded: readonly string[];
35
- }, failureLine: (failure: F) => string, toggle: (parent: string, path: string) => void, open: (path: string) => void): object;
42
+ }, failureLine: (failure: F) => string, toggle: (parent: string, path: string) => void, open: (path: string) => void, current?: NativeTreeCurrent | undefined): object;
36
43
  export {};
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Compatibility adapter: which foreign-service entries the process fence can
3
+ * admit.
4
+ *
5
+ * The upstream experimental forge bundle composes `ctx.sandbox.confine` +
6
+ * `ctx.subprocess.spawn` on its own code paths — the panel's Git reads and the
7
+ * worktree service's creation/restore commands — bypassing every wrapped
8
+ * executor seam the admission already reaches. The fence therefore wraps each
9
+ * reachable async public entry with an ordered admission of its own. Which
10
+ * services and keys exist is a per-release shape: the verified baseline here
11
+ * is `@deepseek-ai/dsh-experimental-forge-bundle@0.2.2-rc.1` (which pins
12
+ * `dsh-experimental-worktree` and `dsh-experimental-client-ui-forge` at the
13
+ * same version), and the `follow`/`changeRequestStates` async generators are
14
+ * deliberately absent — a generator cannot hold a synchronous admission, so
15
+ * its detached work falls back to the minted-profile ledger.
16
+ *
17
+ * @module @dsh-electron/dsh-plugin-multi-root-workspace/compat/forge-entrypoints
18
+ */
19
+ import type { Session } from '@deepseek-ai/dsh-session';
20
+ /** One foreign service whose public entries spawn confined processes. */
21
+ export interface ForeignProcessService {
22
+ readonly service: string;
23
+ readonly keys: readonly string[];
24
+ }
25
+ /**
26
+ * The forge-bundle service entries an admission can wrap, in this order.
27
+ * `worktrees.create`/`restore` take an `Agent` (the session rides on
28
+ * `agent.session`); every listed `forgePanel` entry takes the `Session` as
29
+ * its first argument. Entries not listed — background inspections, the
30
+ * isolated `forge-worktree` instance inside the panel's mount group — stay
31
+ * ledger-covered.
32
+ */
33
+ export declare const FORGE_PROCESS_SERVICES: readonly ForeignProcessService[];
34
+ /**
35
+ * Read the session a foreign-service entry resolves its policy from — the
36
+ * first argument alone: a `Session`, or an `Agent`-shaped value carrying one.
37
+ * The probe is deliberately structural and narrow (`id` + `header`): it is a
38
+ * trust boundary over the upstream type, not a full validation — anything
39
+ * else runs unadmitted rather than let `sandboxPolicy.resolve` read fields a
40
+ * foreign argument lacks.
41
+ * @param first - the wrapped call's first argument.
42
+ * @returns the session, or `undefined` for a call that does not carry one.
43
+ */
44
+ export declare function sessionOf(first: unknown): Session | undefined;
@@ -9,9 +9,40 @@ export type FileActivity = {
9
9
  failed: boolean;
10
10
  };
11
11
  export declare function ptcFileActivity(event: unknown): FileActivity | undefined;
12
+ export declare function stableOpsJson(value: unknown): string;
13
+ export declare const opsActivityEvidenceKey: (kind: "native" | "ptc", id: string, name: string, args: unknown) => string;
14
+ export interface OpsActivityEvidence {
15
+ readonly targets: readonly OpsFileTarget[];
16
+ readonly complete: boolean;
17
+ }
12
18
  /** Rebuild read-only discovery candidates from successful durable activity after resume. */
13
- export declare function replayedInstructionTargets(events: readonly unknown[], origin: string | undefined): readonly string[];
19
+ export declare function replayedInstructionTargets(events: readonly unknown[], origin: string | undefined, liveOpsEvidence?: ReadonlyMap<string, OpsActivityEvidence>, pendingExec?: {
20
+ readonly callId: string;
21
+ readonly name: string;
22
+ readonly arguments: unknown;
23
+ readonly parent?: unknown;
24
+ }, options?: {
25
+ readonly opsCompatibility?: boolean;
26
+ }): readonly string[];
14
27
  /** fs tools whose success touches the file at their `file_path` argument. */
15
28
  export declare const FILE_TOUCH_TOOL_NAMES: Record<string, true>;
16
29
  /** One explicit fs-tool target, shared with live touch capture. */
17
30
  export declare function touchedPathOfToolCall(name: string, argumentsJson: string): string | undefined;
31
+ export interface OpsFileActivityInput {
32
+ name: string;
33
+ args: unknown;
34
+ cwd: string | undefined;
35
+ finalValue: unknown;
36
+ observedPaths: readonly string[];
37
+ isError?: boolean;
38
+ }
39
+ export interface OpsFileTarget {
40
+ path: string;
41
+ kind: 'read' | 'write' | 'remove';
42
+ }
43
+ export interface OpsFileActivity {
44
+ targets: readonly OpsFileTarget[];
45
+ complete: boolean;
46
+ }
47
+ export declare function normalizeOpsFileActivity(input: OpsFileActivityInput): OpsFileActivity;
48
+ export declare function pendingInstructionTargets(name: string, args: unknown): readonly string[];
@@ -0,0 +1,17 @@
1
+ /** Keep the optional host confirmation component behind a structural probe. */
2
+ import type { ComponentType } from 'react';
3
+ export interface OpsConfirmationProps {
4
+ open: boolean;
5
+ title: string;
6
+ description: string;
7
+ acknowledgeLabel: string;
8
+ cancelLabel: string;
9
+ closeLabel: string;
10
+ confirmLabel: string;
11
+ acknowledged: boolean;
12
+ disabled?: boolean;
13
+ onAcknowledgedChange(value: boolean): void;
14
+ onCancel(): void;
15
+ onConfirm(): void;
16
+ }
17
+ export declare function opsRiskConfirmation(): ComponentType<OpsConfirmationProps> | undefined;
@@ -0,0 +1,11 @@
1
+ import type { Context, Fiber } from '@deepseek-ai/cordis';
2
+ export declare const OPS_SETTINGS_ID = "multi-root-ops-compatibility";
3
+ /** Product availability policy, independent of structural Settings adaptation. */
4
+ export declare function opsCompatibilityRelease(release: string | undefined): boolean;
5
+ /** Unsupported hosts retain the config input but expose no editable live field. */
6
+ export declare function opsCompatibilityField<S>(schema: S): S;
7
+ export declare function installOpsCompatibilitySettings(ctx: Context, initial: () => unknown, changed: () => void): {
8
+ read(): unknown;
9
+ fiber: Fiber;
10
+ };
11
+ export declare function opsCompatibilityToken(ctx: Context): object | undefined;
@@ -0,0 +1,25 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ import type { MultiRootScopeService } from '../scope.ts';
3
+ import type { ToolCaller } from './tool-context.ts';
4
+ export declare const OPS_TOOL_PREFIX = "ops_";
5
+ export declare const OPS_EXEC_NAMES: readonly string[];
6
+ export type OpsToolKind = 'exec' | 'publish' | 'unknown' | 'other';
7
+ export declare function classifyOpsTool(name: string): OpsToolKind;
8
+ export interface OpsProtectionState {
9
+ owner: object | undefined;
10
+ entered: number;
11
+ readonly drainWaiters: Set<() => void>;
12
+ }
13
+ export declare function withOpsSpawnPermit<T>(state: OpsProtectionState, body: () => T): T;
14
+ export declare function opsSpawnPermitted(state: OpsProtectionState): boolean;
15
+ export declare function createOpsProtectionState(): OpsProtectionState;
16
+ export declare function claimOpsProtection(state: OpsProtectionState): object;
17
+ export declare function revokeOpsProtection(state: OpsProtectionState, token: object): void;
18
+ export declare function opsProtectionReady(state: OpsProtectionState): boolean;
19
+ export declare function assertOpsProtectionReady(state: OpsProtectionState): void;
20
+ export declare function assertOpsProtectionOwner(state: OpsProtectionState, token: object): void;
21
+ export declare function enterOpsProtection(state: OpsProtectionState): object;
22
+ export declare function leaveOpsProtection(state: OpsProtectionState): void;
23
+ export declare function drainOpsProtection(state: OpsProtectionState): Promise<void>;
24
+ export declare function assertOpsPosixCacheAdmission(deniedRoots: readonly string[]): void;
25
+ export declare function opsDispatchBarrier(ctx: Context, scope: MultiRootScopeService, exec: ToolCaller): string | undefined;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Compatibility adapter for "does this session exist in durable storage".
3
+ *
4
+ * The panel session anchor (ADR-0008) is a live Session in this process's
5
+ * `SessionStore`. A persisted session owned by another Host — or one still
6
+ * mid-resume — never enters that store, so `resolvePanelPrimaryRoot` and the
7
+ * `rootsViewOf` re-check both answer `session-not-found`. That code is true
8
+ * but diagnostically wrong when the session exists: the panel copy asked the
9
+ * user to open a session already on screen.
10
+ *
11
+ * `sessionPersistence.stat(id)` is the cheapest probe of the durable fact:
12
+ * it reads only the session header — never the event log, never the
13
+ * cross-process write lease — and reports `undefined` for an absent id.
14
+ * This module isolates the probe because the fact's meaning differs by
15
+ * outcome:
16
+ *
17
+ * - snapshot returned → the session exists (writer-held elsewhere, still
18
+ * resuming, or needing a format migration — `stat` reports the header in
19
+ * every one of those states): reclassify as `session-not-live`;
20
+ * - `undefined` → the session does not exist in this backend: keep
21
+ * `session-not-found`;
22
+ * - service or `stat` absent (a composition without persistence, or a
23
+ * runtime whose backend does not name it) → `false`: the probe cannot
24
+ * prove existence, so the caller keeps the code it already had;
25
+ * - `stat` throws → `false`, same conservatism. One imprecision survives:
26
+ * a session that exists but whose header cannot be read (corrupt durable
27
+ * state, a broken backend) is reported as not-found rather than not-live —
28
+ * identical to the pre-probe behavior, and preferable to claiming
29
+ * existence the probe failed to prove.
30
+ *
31
+ * @module @dsh-electron/dsh-plugin-multi-root-workspace/compat/session-existence
32
+ */
33
+ import type { Context } from '@deepseek-ai/cordis';
34
+ /**
35
+ * Whether `sessionId` exists in the mounted persistence backend.
36
+ * @param ctx - the plugin fiber context; optional services read via `ctx.get`.
37
+ * @param sessionId - the panel request's session id.
38
+ * @param signal - the request's cancellation, forwarded when the backend takes options.
39
+ * @returns `true` only when a snapshot proves the session exists.
40
+ */
41
+ export declare function persistedSessionExists(ctx: Context, sessionId: string, signal?: AbortSignal): Promise<boolean>;
@@ -16,6 +16,7 @@ export interface SkillsForm {
16
16
  value: boolean;
17
17
  }[], revision?: number): Promise<boolean>;
18
18
  }
19
+ export declare function bindSettingsForm(ctx: Context, entryId: string): SkillsForm | undefined;
19
20
  export declare function bindSkillsForm(ctx: Context): SkillsForm | undefined;
20
21
  export declare function clientInject(ctx: Context, services: string[], callback: (scope: Context) => void | (() => void)): void;
21
22
  export interface NamespaceRule {
@@ -5,5 +5,9 @@ export interface ToolCaller {
5
5
  readonly agent?: Agent;
6
6
  readonly callId: string;
7
7
  readonly name: string;
8
+ readonly arguments?: unknown;
9
+ readonly signal?: AbortSignal;
10
+ readonly parent?: unknown;
11
+ readonly token?: object;
8
12
  }
9
13
  export declare function aroundTool(ctx: Context, listener: (exec: ToolCaller, next: () => Promise<unknown>) => Promise<unknown>): void;
@@ -57,8 +57,8 @@ export declare class DshCompatUnsupportedError extends Error {
57
57
  * `./compat/` detect the shape they need STRUCTURALLY — whether a return value
58
58
  * is a thenable, which export name a module carries — because a structural
59
59
  * probe also survives upstream reshaping something within one release, while a
60
- * version comparison does not. The release string here is for the gate and for
61
- * diagnostics.
60
+ * version comparison does not. The release string here is for installation and
61
+ * feature-availability gates (such as the OPS 0.2.* policy), and diagnostics.
62
62
  */
63
63
  export declare class MultiRootCompatService extends Service {
64
64
  /** The verdict this composition started under. */
@@ -4,7 +4,12 @@ declare module '@deepseek-ai/cordis' {
4
4
  interface Context {
5
5
  multiRootInstructions: {
6
6
  knownRoots(session: object): readonly string[];
7
- snapshot(agent: Agent, roots: readonly string[], target: string | undefined, signal: AbortSignal): Promise<readonly CurrentRootInstruction[]>;
7
+ snapshot(agent: Agent, roots: readonly string[], target: string | readonly string[] | undefined, signal: AbortSignal, pending?: {
8
+ readonly callId: string;
9
+ readonly name: string;
10
+ readonly arguments: unknown;
11
+ readonly parent?: unknown;
12
+ }): Promise<readonly CurrentRootInstruction[]>;
8
13
  };
9
14
  }
10
15
  }