@dsh-electron/dsh-plugin-multi-root-workspace 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.en.md +51 -1
- package/CHANGELOG.md +51 -1
- package/README.en.md +155 -92
- package/README.md +156 -91
- package/cordis.patch.yml +69 -4
- package/lib/client.js +2678 -276
- package/lib/client.js.map +1 -1
- package/lib/command-CESuwlVt.js +1306 -0
- package/lib/command.js +2 -2
- package/lib/compat-DR2LvmYZ.js +600 -0
- package/lib/compat.js +1 -1
- package/lib/contract-DZ4zczDT.js +238 -0
- package/lib/{fs-D_Sw3o5z.js → fs-Nlp2AiRX.js} +56 -24
- package/lib/fs.js +2 -1
- package/lib/index.js +10 -8
- package/lib/{instructions-DfzN-wVf.js → instructions-D19jRxN0.js} +3 -2
- package/lib/instructions.js +1 -1
- package/lib/lsp.js +7 -2
- package/lib/method-wrapper-CFY3p3j2.js +26 -0
- package/lib/process-fence.js +280 -0
- package/lib/providers.js +87 -0
- package/lib/{registry-DySMbjgW.js → registry-DCxC5Lay.js} +256 -35
- package/lib/registry.js +2 -2
- package/lib/{roots-DasWEBJk.js → roots-B-0l8mNG.js} +117 -20
- package/lib/sandbox-CVAcT_gy.js +743 -0
- package/lib/sandbox.js +2 -1
- package/lib/scope.js +229 -20
- package/lib/types/client/RootSwitcher.d.ts +63 -0
- package/lib/types/client/WorkspaceFoldersAction.d.ts +39 -1
- package/lib/types/client/files-tree/FilesBody.module.css.d.ts +24 -0
- package/lib/types/client/files-tree/FilesTree.d.ts +47 -0
- package/lib/types/client/files-tree/adapter.d.ts +94 -0
- package/lib/types/client/files-tree/directory-node.d.ts +62 -0
- package/lib/types/client/files-tree/face.d.ts +107 -0
- package/lib/types/client/files-tree/locales.d.ts +10 -0
- package/lib/types/client/files-tree/primitives.d.ts +33 -0
- package/lib/types/client/files-tree/store.d.ts +81 -0
- package/lib/types/client/locales.d.ts +56 -10
- package/lib/types/client/open-in-app/OpenInAppAction.d.ts +27 -0
- package/lib/types/client/open-in-app/OpenTargetButton.d.ts +52 -0
- package/lib/types/client/open-in-app/OpenTargetButton.module.css.d.ts +29 -0
- package/lib/types/client/open-in-app/adapter.d.ts +46 -0
- package/lib/types/client/open-in-app/applications.d.ts +4 -0
- package/lib/types/client/open-in-app/controller.d.ts +53 -0
- package/lib/types/client/open-in-app/locales.d.ts +10 -0
- package/lib/types/client/open-in-app/open-failure-toast.d.ts +11 -0
- package/lib/types/client/open-in-app/open-path.d.ts +13 -0
- package/lib/types/client/panel-client.d.ts +2 -1
- package/lib/types/client/styles.d.ts +1 -1
- package/lib/types/command.d.ts +16 -3
- package/lib/types/compat/client-icons.d.ts +18 -0
- package/lib/types/compat/dsh-version.d.ts +57 -9
- package/lib/types/compat/open-in-app-routes.d.ts +38 -0
- package/lib/types/compat/process-entrypoints.d.ts +31 -0
- package/lib/types/compat/process-result.d.ts +14 -0
- package/lib/types/compat/pty-marker.d.ts +83 -0
- package/lib/types/compat/sandbox-confine.d.ts +2 -0
- package/lib/types/compat/storage-zod.d.ts +65 -0
- package/lib/types/compat/windows-deny-preflight.d.ts +21 -0
- package/lib/types/compat/windows-native.d.ts +68 -0
- package/lib/types/compat/windows-path-guard.d.ts +56 -0
- package/lib/types/compat/workspace-files.d.ts +2 -0
- package/lib/types/compat.d.ts +15 -5
- package/lib/types/contract.d.ts +31 -19
- package/lib/types/dialects.d.ts +99 -3
- package/lib/types/fs.d.ts +32 -10
- package/lib/types/index.d.ts +8 -3
- package/lib/types/process-fence.d.ts +54 -0
- package/lib/types/profile-isolation.d.ts +178 -0
- package/lib/types/providers.d.ts +70 -0
- package/lib/types/registry.d.ts +132 -31
- package/lib/types/roots.d.ts +100 -13
- package/lib/types/sandbox.d.ts +59 -3
- package/lib/types/scope.d.ts +200 -14
- package/lib/types/windows-broker.d.ts +17 -0
- package/lib/types/windows-deny.d.ts +1 -0
- package/lib/types/windows-profile.d.ts +10 -13
- package/lib/types/windows-runner-contract.d.ts +97 -0
- package/lib/windows-runner-contract-CEfmZzu0.js +141 -0
- package/lib/windows-runner.js +1256 -13
- package/lib/workspace-files.js +138 -3
- package/lib/workspace-routing-YPxaQ4V3.js +26 -0
- package/package.json +84 -62
- package/scripts/windows-broker/install.ps1 +89 -0
- package/scripts/windows-broker/rights.cs +58 -0
- package/scripts/windows-broker/service.cs +323 -0
- package/scripts/windows-broker/verify-install.ps1 +80 -0
- package/lib/command-CTdNvwt1.js +0 -791
- package/lib/compat-B6ZqbQBy.js +0 -295
- package/lib/method-wrapper-Zc-JlDP3.js +0 -51
- package/lib/sandbox-BRXl_FpF.js +0 -132
- package/lib/types/client/RootFileTree.d.ts +0 -12
- package/lib/types/panel-files.d.ts +0 -3
- package/lib/windows-profile-CiFsFYIV.js +0 -216
- package/lib/{registry-lease-win32-dy8TQMD9.js → registry-lease-win32-DTO6QpT9.js} +2 -2
package/lib/sandbox.js
CHANGED
package/lib/scope.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import { t as aroundTool } from "./tool-context-CpgIFcCB.js";
|
|
2
|
+
import { r as effectiveRootAccess } from "./contract-DZ4zczDT.js";
|
|
1
3
|
import { Service } from "@deepseek-ai/cordis";
|
|
2
|
-
import { canonicalPath } from "@deepseek-ai/dsh-sandbox";
|
|
3
4
|
import { statSync } from "node:fs";
|
|
5
|
+
import { isAbsolute } from "node:path";
|
|
6
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
7
|
+
import { canonicalPath } from "@deepseek-ai/dsh-sandbox";
|
|
4
8
|
//#region src/scope.ts
|
|
5
9
|
/**
|
|
6
10
|
* The multi-root workspace scope: the plugin's single home for the question
|
|
@@ -38,9 +42,14 @@ import { statSync } from "node:fs";
|
|
|
38
42
|
* @returns the sanitized additional roots.
|
|
39
43
|
*/
|
|
40
44
|
function sanitizeAdditionalRoots(primaryRoot, roots) {
|
|
45
|
+
return sanitizeMembers(primaryRoot, roots).map((root) => root.path);
|
|
46
|
+
}
|
|
47
|
+
/** The member-level form of {@link sanitizeAdditionalRoots}: same rules, with access kept. */
|
|
48
|
+
function sanitizeMembers(primaryRoot, roots) {
|
|
41
49
|
const seen = /* @__PURE__ */ new Set([primaryRoot]);
|
|
42
50
|
const result = [];
|
|
43
51
|
for (const root of roots) {
|
|
52
|
+
if (root.withheld) continue;
|
|
44
53
|
const recorded = root.recordedPath;
|
|
45
54
|
if (typeof recorded !== "string" || recorded === "") continue;
|
|
46
55
|
const canonical = canonicalPath(root.path);
|
|
@@ -52,7 +61,12 @@ function sanitizeAdditionalRoots(primaryRoot, roots) {
|
|
|
52
61
|
}
|
|
53
62
|
if (seen.has(canonical)) continue;
|
|
54
63
|
seen.add(canonical);
|
|
55
|
-
result.push(
|
|
64
|
+
result.push({
|
|
65
|
+
path: canonical,
|
|
66
|
+
access: root.access,
|
|
67
|
+
id: root.id,
|
|
68
|
+
...root.alias === void 0 ? {} : { alias: root.alias }
|
|
69
|
+
});
|
|
56
70
|
}
|
|
57
71
|
return result;
|
|
58
72
|
}
|
|
@@ -62,18 +76,75 @@ function sanitizeAdditionalRoots(primaryRoot, roots) {
|
|
|
62
76
|
*/
|
|
63
77
|
const MULTI_ROOT_CONTEXT_NAME = "multi-root:scope";
|
|
64
78
|
/**
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
*
|
|
79
|
+
* The deterministic sentence the model hears while the registry authority is
|
|
80
|
+
* unavailable under either writable mode — enforcement is then the
|
|
81
|
+
* primary-root-only one, which the prompt must not leave implicit.
|
|
82
|
+
*/
|
|
83
|
+
const REGISTRY_UNAVAILABLE_NOTE = "The DSH multi-root registry is not available in this process, so writes are temporarily limited to the primary root; shared platform temp directories are not writable. Explicit deny boundaries still take precedence.";
|
|
84
|
+
/**
|
|
85
|
+
* The B9 effective-access rule (ADR-0017's nine-cell matrix): whether a root
|
|
86
|
+
* with this registration policy is writable under this session mode.
|
|
87
|
+
* `read-write` is writable everywhere; `inherit` follows the mode;
|
|
88
|
+
* `read-only` never is.
|
|
72
89
|
*/
|
|
73
|
-
function
|
|
74
|
-
return
|
|
90
|
+
function effectivelyWritable(mode, access) {
|
|
91
|
+
return effectiveRootAccess(access, mode) !== "read-only";
|
|
75
92
|
}
|
|
76
93
|
/**
|
|
94
|
+
* Render the workspace topology the model is told about. Pure: the text
|
|
95
|
+
* depends only on its inputs, so two consecutive assemblies of the same
|
|
96
|
+
* session state produce the same contribution byte for byte. Only granted
|
|
97
|
+
* members are ever listed — withheld, missing, or invalid roots never reach
|
|
98
|
+
* `roots` in the first place — and no host internals (ids, SIDs, store or
|
|
99
|
+
* lease paths) are ever named.
|
|
100
|
+
* @param options.platform - the host platform (`process.platform` of the contribution).
|
|
101
|
+
* @param options.mode - the session's sandbox mode.
|
|
102
|
+
* @param options.primaryRoot - the canonical session workspace root.
|
|
103
|
+
* @param options.roots - the granted additional members, in registry order.
|
|
104
|
+
* @param options.cappedTools - sorted tool names that can never write an additional root.
|
|
105
|
+
* @param options.toolAccessInvalid - invalid host config caps every additional-root write.
|
|
106
|
+
* @param options.authority - the registry-authority fact; `unavailable` under
|
|
107
|
+
* `danger-full-access` and `workspace-write` limit writes to the primary root, which the
|
|
108
|
+
* model must hear even when there is no topology to describe.
|
|
109
|
+
* @param options.protectedHome - the canonical DSH home when the scope appended
|
|
110
|
+
* it to the deny set (`danger-full-access` with restrictions); stated by path
|
|
111
|
+
* so the model can place the refusal.
|
|
112
|
+
* @returns the stable topology text; empty when there are no additional roots
|
|
113
|
+
* and no downgrade to report.
|
|
114
|
+
*/
|
|
115
|
+
function renderWorkspaceRootsContext(options) {
|
|
116
|
+
const { platform, mode, primaryRoot, roots, cappedTools } = options;
|
|
117
|
+
const lead = [options.authority === "unavailable" && mode !== "read-only" ? REGISTRY_UNAVAILABLE_NOTE : "", options.protectedHome !== void 0 && mode === "danger-full-access" ? `Writes under the DSH home directory at ${JSON.stringify(options.protectedHome)} are refused.` : ""].filter((note) => note !== "").join(" ");
|
|
118
|
+
if (options.authority === "unavailable") return lead;
|
|
119
|
+
if (roots.length === 0) return lead;
|
|
120
|
+
const prefix = lead === "" ? "" : `${lead} `;
|
|
121
|
+
const members = JSON.stringify(roots.map((root) => root.path));
|
|
122
|
+
const readOnly = roots.filter((root) => root.access === "read-only").map((root) => root.path);
|
|
123
|
+
const capNote = cappedTools.length === 0 ? "" : ` The tools ${JSON.stringify(cappedTools)} cannot write additional roots even where they are writable.`;
|
|
124
|
+
if (mode === "read-only") {
|
|
125
|
+
const writable = options.toolAccessInvalid ? [] : roots.filter((root) => root.access === "read-write").map((root) => root.path);
|
|
126
|
+
if (writable.length === 0) return prefix + `Current DSH workspace roots: ${members} are additional roots of this session's workspace and are readable; nothing is writable in read-only mode.` + capNote;
|
|
127
|
+
const readable = roots.filter((root) => root.access !== "read-write").map((root) => root.path);
|
|
128
|
+
let text = `Current DSH workspace roots: ${JSON.stringify(writable)} are additional roots that stay writable even in read-only mode`;
|
|
129
|
+
if (readable.length > 0) text += `, and ${JSON.stringify(readable)} are readable additional roots`;
|
|
130
|
+
return prefix + text + " of this session's workspace; everything else is read-only." + capNote;
|
|
131
|
+
}
|
|
132
|
+
if (mode === "danger-full-access") {
|
|
133
|
+
const refused = options.toolAccessInvalid ? roots.map((root) => root.path) : readOnly;
|
|
134
|
+
return prefix + `Current DSH workspace roots: ${members} are additional roots of this session's workspace; danger-full-access mode has no sandbox isolation` + (refused.length > 0 ? `, except writes under the additional roots ${JSON.stringify(refused)} are still refused${options.toolAccessInvalid === true ? " because toolAccess is invalid" : ""}. Grant changes apply to later operations and newly started processes, not to processes already running.` : ".") + capNote;
|
|
135
|
+
}
|
|
136
|
+
const writable = options.toolAccessInvalid ? [] : roots.filter((root) => root.access !== "read-only").map((root) => root.path);
|
|
137
|
+
const groups = [];
|
|
138
|
+
if (options.toolAccessInvalid) groups.push(`${members} are readable additional roots (all additional-root writes are refused because toolAccess is invalid)`);
|
|
139
|
+
if (writable.length > 0) groups.push(`${JSON.stringify(writable)} are writable additional roots`);
|
|
140
|
+
if (!options.toolAccessInvalid && readOnly.length > 0) groups.push(`${JSON.stringify(readOnly)} are read-only additional roots (writes are refused)`);
|
|
141
|
+
let text = `Current DSH workspace roots: ${groups.join(" and ")} of this session's workspace. The session origin remains the primary root (${JSON.stringify(primaryRoot)}); changing the working directory does not change these grants. Grant changes apply to later operations and newly started processes, not to processes already running.`;
|
|
142
|
+
if (platform === "win32" && !options.toolAccessInvalid) text += " Writable additional roots are enforced for shell processes by the upstream Windows ACL restricted-token sandbox, whose enforcement is reported as partial; reads are not confined by it.";
|
|
143
|
+
return prefix + text + capNote;
|
|
144
|
+
}
|
|
145
|
+
/** The tool-dispatch cap carried across async boundaries by the waterfall. */
|
|
146
|
+
const toolCapStore = new AsyncLocalStorage();
|
|
147
|
+
/**
|
|
77
148
|
* The `ctx.multiRootScope` service. Providers ask it — never the filesystem —
|
|
78
149
|
* what the current scope is, which is what keeps the fs fence and every kernel
|
|
79
150
|
* dialect on one permission world (requirement §13).
|
|
@@ -82,6 +153,26 @@ var MultiRootScopeService = class extends Service {
|
|
|
82
153
|
/** Additional roots keyed by canonical primary root; empty until roots are registered. */
|
|
83
154
|
rootsByPrimary = /* @__PURE__ */ new Map();
|
|
84
155
|
listeners = /* @__PURE__ */ new Set();
|
|
156
|
+
/**
|
|
157
|
+
* The registry-authority fact the registry row publishes. Defaults to
|
|
158
|
+
* `available`: a composition without a registry row can never have
|
|
159
|
+
* registered roots, so nothing is unknown there. The registry sets it false
|
|
160
|
+
* the moment it cannot prove the store is its own — before the lease is
|
|
161
|
+
* acquired, while contended or storage-failed, and at teardown — and true
|
|
162
|
+
* only after the accepted records have been published.
|
|
163
|
+
*/
|
|
164
|
+
registryAuthority = "available";
|
|
165
|
+
/** Validated `toolAccess` row config; empty when no tool is capped. */
|
|
166
|
+
caps;
|
|
167
|
+
/** A malformed `toolAccess` refuses every additional-root write (fail closed). */
|
|
168
|
+
capsInvalid;
|
|
169
|
+
/**
|
|
170
|
+
* The configured DSH home boundary; `undefined` when the row carries no
|
|
171
|
+
* `dshHome` (bare test compositions) or one that is not an absolute string —
|
|
172
|
+
* an invalid value is logged at construction and ignored rather than
|
|
173
|
+
* treated as evidence.
|
|
174
|
+
*/
|
|
175
|
+
dshHome;
|
|
85
176
|
/** Observe registry publications; consumers still resolve authority from scopeOf(). */
|
|
86
177
|
subscribe(listener) {
|
|
87
178
|
this.listeners.add(listener);
|
|
@@ -89,8 +180,39 @@ var MultiRootScopeService = class extends Service {
|
|
|
89
180
|
this.listeners.delete(listener);
|
|
90
181
|
};
|
|
91
182
|
}
|
|
92
|
-
|
|
183
|
+
/**
|
|
184
|
+
* The sorted names of the tools `toolAccess` caps — read-only for the
|
|
185
|
+
* prompt contribution. An invalid config is not enumerated (it caps every
|
|
186
|
+
* call but is not a listable tool set).
|
|
187
|
+
*/
|
|
188
|
+
toolCaps() {
|
|
189
|
+
return Object.keys(this.caps).sort();
|
|
190
|
+
}
|
|
191
|
+
/** Whether malformed toolAccess caps every call, including unknown tools. */
|
|
192
|
+
get toolAccessInvalid() {
|
|
193
|
+
return this.capsInvalid;
|
|
194
|
+
}
|
|
195
|
+
constructor(ctx, config = {}) {
|
|
93
196
|
super(ctx, "multiRootScope");
|
|
197
|
+
const toolAccess = config.toolAccess;
|
|
198
|
+
if (toolAccess === void 0) {
|
|
199
|
+
this.caps = {};
|
|
200
|
+
this.capsInvalid = false;
|
|
201
|
+
} else if (typeof toolAccess !== "object" || toolAccess === null || Array.isArray(toolAccess) || Object.values(toolAccess).some((value) => value !== "read-only")) {
|
|
202
|
+
this.caps = {};
|
|
203
|
+
this.capsInvalid = true;
|
|
204
|
+
ctx.logger.error(`multi-root-workspace: invalid toolAccess config (expected an object mapping tool names to "read-only", got ${JSON.stringify(toolAccess)}); no additional root is writable`);
|
|
205
|
+
} else {
|
|
206
|
+
this.caps = { ...toolAccess };
|
|
207
|
+
this.capsInvalid = false;
|
|
208
|
+
}
|
|
209
|
+
const dshHome = config.dshHome;
|
|
210
|
+
if (dshHome !== void 0 && (typeof dshHome !== "string" || dshHome.trim() === "" || !isAbsolute(dshHome))) ctx.logger.error(`multi-root-workspace: invalid dshHome config (expected an absolute path, got ${JSON.stringify(dshHome)}); the DSH-home deny boundary is disabled`);
|
|
211
|
+
else this.dshHome = dshHome;
|
|
212
|
+
aroundTool(ctx, (exec, next) => {
|
|
213
|
+
const parent = toolCapStore.getStore();
|
|
214
|
+
return toolCapStore.run({ capped: parent?.capped === true || this.caps[exec.name] === "read-only" }, next);
|
|
215
|
+
});
|
|
94
216
|
ctx.inject(["systemPrompt", "sandboxPolicy"], (scope) => {
|
|
95
217
|
scope.systemPrompt.context({
|
|
96
218
|
name: MULTI_ROOT_CONTEXT_NAME,
|
|
@@ -99,10 +221,18 @@ var MultiRootScopeService = class extends Service {
|
|
|
99
221
|
const session = assembly.agent?.session;
|
|
100
222
|
if (session === void 0) return "";
|
|
101
223
|
const policy = scope.sandboxPolicy.resolve({ session });
|
|
102
|
-
if (policy.mode !== "workspace-write") return "";
|
|
103
224
|
const resolved = this.resolve(policy);
|
|
104
|
-
if (resolved.
|
|
105
|
-
return renderWorkspaceRootsContext(
|
|
225
|
+
if (resolved.roots.length === 0 && resolved.authority !== "unavailable" && resolved.protectedHome === void 0) return "";
|
|
226
|
+
return renderWorkspaceRootsContext({
|
|
227
|
+
platform: process.platform,
|
|
228
|
+
mode: policy.mode,
|
|
229
|
+
primaryRoot: resolved.primaryRoot,
|
|
230
|
+
roots: resolved.roots,
|
|
231
|
+
cappedTools: this.toolCaps(),
|
|
232
|
+
toolAccessInvalid: this.toolAccessInvalid,
|
|
233
|
+
authority: resolved.authority,
|
|
234
|
+
...resolved.protectedHome === void 0 ? {} : { protectedHome: resolved.protectedHome }
|
|
235
|
+
});
|
|
106
236
|
}
|
|
107
237
|
});
|
|
108
238
|
});
|
|
@@ -114,20 +244,86 @@ var MultiRootScopeService = class extends Service {
|
|
|
114
244
|
*/
|
|
115
245
|
resolve(policy) {
|
|
116
246
|
const primaryRoot = canonicalPath(policy.workspaceRoot);
|
|
247
|
+
const roots = this.membersOf(primaryRoot);
|
|
248
|
+
const capped = this.capped();
|
|
249
|
+
const writable = capped || this.registryAuthority === "unavailable" ? [] : roots.filter((root) => effectivelyWritable(policy.mode, root.access)).map((root) => root.path);
|
|
250
|
+
const denied = (this.rootsByPrimary.get(primaryRoot) ?? []).filter((root) => typeof root.recordedPath === "string" && root.recordedPath !== "" && root.recordedPath !== primaryRoot && (capped || !effectivelyWritable(policy.mode, root.access))).map((root) => root.recordedPath);
|
|
251
|
+
const deniedRoots = [...new Set(denied)];
|
|
252
|
+
let protectedHome;
|
|
253
|
+
if (this.dshHome !== void 0 && policy.mode === "danger-full-access" && (deniedRoots.length > 0 || this.registryAuthority === "unavailable")) {
|
|
254
|
+
const home = canonicalPath(this.dshHome);
|
|
255
|
+
if (!deniedRoots.includes(home)) {
|
|
256
|
+
deniedRoots.push(home);
|
|
257
|
+
protectedHome = home;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
117
260
|
return {
|
|
118
261
|
primaryRoot,
|
|
119
|
-
additionalRoots:
|
|
262
|
+
additionalRoots: roots.map((root) => root.path),
|
|
263
|
+
roots,
|
|
264
|
+
writableRoots: writable,
|
|
265
|
+
deniedRoots,
|
|
266
|
+
authority: this.registryAuthority,
|
|
267
|
+
...protectedHome === void 0 ? {} : { protectedHome }
|
|
120
268
|
};
|
|
121
269
|
}
|
|
122
270
|
/**
|
|
123
|
-
*
|
|
271
|
+
* Whether THIS call's additional-root writes are capped. Inside a tool
|
|
272
|
+
* dispatch the AsyncLocalStorage store answers; outside any dispatch (user
|
|
273
|
+
* PTY, panel, an unknown async boundary) the answer is fail-closed: capped
|
|
274
|
+
* whenever `toolAccess` names any tool, because an operation whose tool
|
|
275
|
+
* identity is unknown gets the most restrictive configured cap. Membership
|
|
276
|
+
* is never affected — only writability.
|
|
277
|
+
*/
|
|
278
|
+
capped() {
|
|
279
|
+
if (this.capsInvalid) return true;
|
|
280
|
+
const store = toolCapStore.getStore();
|
|
281
|
+
if (store !== void 0) return store.capped;
|
|
282
|
+
return Object.keys(this.caps).length > 0;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* The additional roots registered for one canonical primary root — every
|
|
286
|
+
* granted member, ANY access level (a read-only root is still a member of
|
|
287
|
+
* the workspace for instructions, LSP, routing, and reads).
|
|
124
288
|
* @param primaryRoot - canonical primary root (or any spelling thereof).
|
|
125
289
|
* @returns the canonical additional roots; empty when none are registered.
|
|
126
290
|
*/
|
|
127
291
|
scopeOf(primaryRoot) {
|
|
128
|
-
|
|
292
|
+
return this.membersOf(primaryRoot).map((root) => root.path);
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* The additional roots a WRITE may reach for one primary root under
|
|
296
|
+
* `workspace-write` — the members whose effective access is writable there
|
|
297
|
+
* (`inherit` and fixed `read-write`). Tool caps are per-call and do not
|
|
298
|
+
* apply to this diagnostic projection.
|
|
299
|
+
* @param primaryRoot - canonical primary root (or any spelling thereof).
|
|
300
|
+
* @returns the canonical writable roots; empty when none qualify.
|
|
301
|
+
*/
|
|
302
|
+
writableOf(primaryRoot) {
|
|
303
|
+
return this.membersOf(primaryRoot).filter((root) => root.access !== "read-only").map((root) => root.path);
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Whether ANY registered primary currently publishes an explicit `read-only`
|
|
307
|
+
* member. The PTY seam resolves its caller through the agent-PTY
|
|
308
|
+
* environment markers; when that lookup fails this flag is the difference
|
|
309
|
+
* between pass-through (nothing to deny anywhere on the store) and fail
|
|
310
|
+
* closed. An unavailable registry authority answers `true` by itself: a
|
|
311
|
+
* terminal whose session cannot be resolved must not inherit a deny set
|
|
312
|
+
* this process cannot see.
|
|
313
|
+
* @returns whether at least one primary holds a restricted root, or the
|
|
314
|
+
* registry cannot prove none does.
|
|
315
|
+
*/
|
|
316
|
+
hasActiveDenyRoots() {
|
|
317
|
+
if (this.registryAuthority === "unavailable") return true;
|
|
318
|
+
for (const members of this.rootsByPrimary.values()) if (members.some((root) => root.access === "read-only") || members.length > 0 && this.capped()) return true;
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
/** The sanitized members of one primary root, in registry order. */
|
|
322
|
+
membersOf(primaryRoot) {
|
|
323
|
+
const key = canonicalPath(primaryRoot);
|
|
324
|
+
const registered = this.rootsByPrimary.get(key);
|
|
129
325
|
if (registered === void 0) return [];
|
|
130
|
-
return
|
|
326
|
+
return sanitizeMembers(key, registered);
|
|
131
327
|
}
|
|
132
328
|
/**
|
|
133
329
|
* Replace the roots registered for one primary root.
|
|
@@ -144,6 +340,19 @@ var MultiRootScopeService = class extends Service {
|
|
|
144
340
|
else this.rootsByPrimary.set(key, [...roots]);
|
|
145
341
|
for (const listener of this.listeners) listener();
|
|
146
342
|
}
|
|
343
|
+
/**
|
|
344
|
+
* Publish the registry-authority fact. This is the REGISTRY's write port —
|
|
345
|
+
* the counterpart of {@link setAdditionalRoots}: the registry marks the
|
|
346
|
+
* scope `unavailable` the moment it stops being the store's authority
|
|
347
|
+
* (before acquisition, on contention or storage failure, at teardown) and
|
|
348
|
+
* `available` only once its stored records have been published. Nothing
|
|
349
|
+
* else may call it, and it never touches the root table.
|
|
350
|
+
* @param available - whether this process holds the registry authority.
|
|
351
|
+
*/
|
|
352
|
+
setRegistryAuthority(available) {
|
|
353
|
+
this.registryAuthority = available ? "available" : "unavailable";
|
|
354
|
+
for (const listener of this.listeners) listener();
|
|
355
|
+
}
|
|
147
356
|
};
|
|
148
357
|
//#endregion
|
|
149
|
-
export { MULTI_ROOT_CONTEXT_NAME, MultiRootScopeService, MultiRootScopeService as default, renderWorkspaceRootsContext, sanitizeAdditionalRoots };
|
|
358
|
+
export { MULTI_ROOT_CONTEXT_NAME, MultiRootScopeService, MultiRootScopeService as default, REGISTRY_UNAVAILABLE_NOTE, renderWorkspaceRootsContext, sanitizeAdditionalRoots };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Files-tab root switcher — the entry that took over the header position
|
|
3
|
+
* the P1 gear launcher first claimed. It poses as the pane's path label: a
|
|
4
|
+
* trigger that opens a menu of this session's roots (primary first, the
|
|
5
|
+
* registered additional roots next, and the Workspace Folders manager last),
|
|
6
|
+
* followed by a `PathLabel` of the selected root's full path — the same
|
|
7
|
+
* component upstream draws in that spot, so both primary and additional
|
|
8
|
+
* selections read exactly like the native header. While an additional root
|
|
9
|
+
* is selected it also lends the pane's body area to this plugin's verbatim
|
|
10
|
+
* fork of the native Files tree (`files-tree/`), so an additional root reads
|
|
11
|
+
* and behaves exactly like the primary one.
|
|
12
|
+
*
|
|
13
|
+
* Two upstream DOM hooks make the takeover work, and both degrade silently:
|
|
14
|
+
*
|
|
15
|
+
* - The stylesheet rules: while `.mrfw-rootSwitcher` exists inside a
|
|
16
|
+
* `[data-files-root]` pane the upstream `data-files-path` is hidden and the
|
|
17
|
+
* switcher takes its flex position; while the switcher carries
|
|
18
|
+
* `data-root-kind="additional"` the native `data-files-body`,
|
|
19
|
+
* `data-files-reload`, and `data-open-target="directory"` split button
|
|
20
|
+
* hide too (the reload refreshes the very tree that is no longer
|
|
21
|
+
* visible, and the native open-in-app button only ever receives the
|
|
22
|
+
* primary root through its slot props — the header gets a
|
|
23
|
+
* `.mrfw-switcherReload` replacement and the plugin's verbatim fork of
|
|
24
|
+
* the open-in-app control, pointed at the selected root). Upstream
|
|
25
|
+
* changing that DOM shape simply stops the selectors from matching — the
|
|
26
|
+
* native header keeps working and the switcher is just another actions
|
|
27
|
+
* occupant.
|
|
28
|
+
* - `closest('[data-files-root]')` from the switcher's own element locates
|
|
29
|
+
* the pane the forked Files tree is portaled into, as a sibling of
|
|
30
|
+
* `data-files-body`. No match → no portal: the component keeps working as
|
|
31
|
+
* a menu and the native tree keeps its area. The tree's tab id comes from
|
|
32
|
+
* the same element's `closest('[data-dockkit-content]')` — the dockkit
|
|
33
|
+
* tabHost section — with a generated fallback where the hook is absent.
|
|
34
|
+
*
|
|
35
|
+
* The selected root is component state on purpose: every tab render
|
|
36
|
+
* occurrence gets its own, and reopening the pane resets to the primary
|
|
37
|
+
* root. There is no cross-tab or persisted selection.
|
|
38
|
+
*
|
|
39
|
+
* @module @dsh-electron/dsh-plugin-multi-root-workspace/client/RootSwitcher
|
|
40
|
+
*/
|
|
41
|
+
import { type ReactNode } from 'react';
|
|
42
|
+
import { type FilesTreeSurface } from './files-tree/adapter.tsx';
|
|
43
|
+
import { type OpenInAppAdapter } from './open-in-app/adapter.tsx';
|
|
44
|
+
import { type WorkspaceFoldersActionProps } from './WorkspaceFoldersAction.tsx';
|
|
45
|
+
/** What the registered Files-actions occupant needs beyond the slot owner. */
|
|
46
|
+
export interface RootSwitcherProps extends Omit<WorkspaceFoldersActionProps, 'compact' | 'wide'> {
|
|
47
|
+
/** This pane's workspace root, carried by the slot owner's `absolutePath`. */
|
|
48
|
+
readonly primaryPath: string;
|
|
49
|
+
/**
|
|
50
|
+
* The forked open-in-app control's shared props, or `undefined` where the
|
|
51
|
+
* fork cannot render (an undefined adapter is upstream's own "no button"
|
|
52
|
+
* state, so the switcher just draws nothing there too).
|
|
53
|
+
*/
|
|
54
|
+
readonly openInApp?: OpenInAppAdapter | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* The forked Files tree's shared props, or `undefined` where the fork
|
|
57
|
+
* cannot render — the additional-root rows then stay out of the menu at
|
|
58
|
+
* all, which is the same absence upstream's own tree produces.
|
|
59
|
+
*/
|
|
60
|
+
readonly filesTree?: FilesTreeSurface | undefined;
|
|
61
|
+
}
|
|
62
|
+
/** The Files-header switcher: root menu, dialog entry, and the portal tree. */
|
|
63
|
+
export declare function RootSwitcher(props: RootSwitcherProps): ReactNode;
|
|
@@ -16,11 +16,16 @@
|
|
|
16
16
|
* native look — sidebar trigger, modal card, capsule buttons — in both themes
|
|
17
17
|
* without naming a single color here (see ADR-0006).
|
|
18
18
|
*
|
|
19
|
+
* The dialog itself is also exported: the Files-tab root switcher opens the
|
|
20
|
+
* same manager through its trailing menu item instead of carrying a second
|
|
21
|
+
* dialog.
|
|
22
|
+
*
|
|
19
23
|
* @module @dsh-electron/dsh-plugin-multi-root-workspace/client/WorkspaceFoldersAction
|
|
20
24
|
*/
|
|
21
25
|
import { type ReactNode } from 'react';
|
|
22
26
|
import { type PanelClient } from './panel-client.ts';
|
|
23
27
|
import type { Key } from './locales.ts';
|
|
28
|
+
import type { RootAccess, RootEntryView, RootState, RootView } from '../contract.ts';
|
|
24
29
|
/** Translate one key of this plugin's namespace. */
|
|
25
30
|
export type Translate = (key: Key, params?: Record<string, unknown>) => string;
|
|
26
31
|
/** What the registered action receives from the plugin body. */
|
|
@@ -39,8 +44,41 @@ export interface WorkspaceFoldersActionProps {
|
|
|
39
44
|
readonly subscribeSession?: (listener: () => void) => () => void;
|
|
40
45
|
/** Whether the sidebar renders wide content (`false` in the icon rail). */
|
|
41
46
|
readonly wide?: boolean;
|
|
42
|
-
/**
|
|
47
|
+
/** Icon-only launcher in the upstream Files tab's public actions slot. */
|
|
43
48
|
readonly compact?: boolean;
|
|
44
49
|
}
|
|
50
|
+
/** The locale key for one reported root state. */
|
|
51
|
+
export declare function stateKey(state: RootState): Key;
|
|
52
|
+
/** The locale key for one registration's access level. */
|
|
53
|
+
export declare function accessKey(access: RootAccess): Key;
|
|
54
|
+
/**
|
|
55
|
+
* Whether moving FROM `from` TO `to` could widen the agent's write authority
|
|
56
|
+
* under some session mode. The three levels are not a total order — `inherit`
|
|
57
|
+
* is weaker than `read-write` under a read-only session but never weaker
|
|
58
|
+
* under the others — so the check is spelled out per pair rather than
|
|
59
|
+
* computed from a rank. Only a widening change asks for confirmation.
|
|
60
|
+
*/
|
|
61
|
+
export declare function widensAccess(from: RootAccess, to: RootAccess): boolean;
|
|
62
|
+
/** The final segment of an absolute path, on either platform separator. */
|
|
63
|
+
export declare function basename(path: string): string;
|
|
64
|
+
/**
|
|
65
|
+
* The primary text of one row: the stored alias when set, otherwise the
|
|
66
|
+
* directory's own name — the display the registration was added with.
|
|
67
|
+
*/
|
|
68
|
+
export declare function displayNameOf(root: RootView): string;
|
|
45
69
|
/** The sidebar footer action plus the dialog it opens. */
|
|
46
70
|
export declare function WorkspaceFoldersAction(props: WorkspaceFoldersActionProps): ReactNode;
|
|
71
|
+
/** The exact row reference the browsing endpoints and mutations carry. */
|
|
72
|
+
export declare function entryOf(root: RootView): RootEntryView;
|
|
73
|
+
/**
|
|
74
|
+
* The dialog: reads the view once per open, then after every mutation.
|
|
75
|
+
*
|
|
76
|
+
* Two shapes travel back over the channel, and they are handled separately on
|
|
77
|
+
* purpose: every mutating endpoint answers with the whole refreshed view, while
|
|
78
|
+
* `reveal` answers with the path it revealed and leaves the list untouched
|
|
79
|
+
* (see `contract.ts`). Treating a reveal answer as a roots view is exactly the
|
|
80
|
+
* contract mismatch this split removes.
|
|
81
|
+
*/
|
|
82
|
+
export declare function WorkspaceFoldersDialog(props: WorkspaceFoldersActionProps & {
|
|
83
|
+
onClose: () => void;
|
|
84
|
+
}): ReactNode;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shim for the CSS-module virtual module `./FilesBody.module.css`.
|
|
3
|
+
*
|
|
4
|
+
* This toolchain resolves real files, not CSS-module virtual modules — so
|
|
5
|
+
* the forked body imports this file instead (the one-line specifier delta
|
|
6
|
+
* recorded in FORK.md). It exports the same `css.<name>` map, pointed at the
|
|
7
|
+
* plugin-prefixed `mrfw-ft*` classes whose rules live in `styles.ts`; the
|
|
8
|
+
* upstream `FilesBody.module.css` sits beside it verbatim as the diffable
|
|
9
|
+
* source. Only the names the fork's JSX actually reads are mapped — the
|
|
10
|
+
* header-only entries (`root`, `header`, `path`, `tool`, `status`,
|
|
11
|
+
* `statusLine`, `titleIcon`) belong to the shell this fork drops.
|
|
12
|
+
*/
|
|
13
|
+
declare const css: {
|
|
14
|
+
readonly body: "mrfw-ftBody";
|
|
15
|
+
readonly level: "mrfw-ftLevel";
|
|
16
|
+
readonly item: "mrfw-ftItem";
|
|
17
|
+
readonly row: "mrfw-ftRow";
|
|
18
|
+
readonly icon: "mrfw-ftIcon";
|
|
19
|
+
readonly fileIcon: "mrfw-ftFileIcon";
|
|
20
|
+
readonly name: "mrfw-ftName";
|
|
21
|
+
readonly other: "mrfw-ftOther";
|
|
22
|
+
readonly note: "mrfw-ftNote";
|
|
23
|
+
};
|
|
24
|
+
export default css;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { RemoteFailure } from '@deepseek-ai/dsh-api-remotes/client';
|
|
3
|
+
import type { PropsLocale, PropsStore, TranslateNS } from '@deepseek-ai/dsh-client-ui-slots';
|
|
4
|
+
import type { TabId } from '@deepseek-ai/dsh-client-ui-dockkit';
|
|
5
|
+
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
6
|
+
import type { WorkspaceDirectoryEntry } from '@deepseek-ai/dsh-api-workspace-files/types';
|
|
7
|
+
import type { FilesInjected } from './face.ts';
|
|
8
|
+
import type { createFilesStore } from './store.ts';
|
|
9
|
+
/**
|
|
10
|
+
* The fork's props: the store/locale shares verbatim from upstream's
|
|
11
|
+
* composed props, `start`/`toggle` from the injected face, and the four
|
|
12
|
+
* seams upstream binds inside `FilesBody` — tab id, lifetime, root, and the
|
|
13
|
+
* resource-opening callback — supplied by the adapter instead.
|
|
14
|
+
*/
|
|
15
|
+
export type FilesTreeProps = PropsStore<ReturnType<typeof createFilesStore>> & Pick<FilesInjected, 'start' | 'toggle'> & PropsLocale<'sidebarFiles'> & {
|
|
16
|
+
/** The store bucket this tree reads and writes — the pane's own dockkit tab id. */
|
|
17
|
+
readonly tabId: TabId;
|
|
18
|
+
/** Session the listing and the resource addresses belong to. */
|
|
19
|
+
readonly sessionId: SessionId;
|
|
20
|
+
/** Absolute path of the root this tree lists (the selected additional root). */
|
|
21
|
+
readonly root: string;
|
|
22
|
+
/** Takeover lifetime: aborting forgets the bucket and closes the watch tree. */
|
|
23
|
+
readonly signal: AbortSignal;
|
|
24
|
+
/** Open one `dsh-resource://file` address (the adapter's sidebarRight route). */
|
|
25
|
+
readonly onOpen: (address: string) => void;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Order one level's entries for display: directories first, then everything
|
|
29
|
+
* else, each group by name. The endpoint's order is a listing fact; this is the
|
|
30
|
+
* reader's.
|
|
31
|
+
* @param entries - the listing as the endpoint returned it.
|
|
32
|
+
* @returns a new array, directories first, then by name within each group.
|
|
33
|
+
*/
|
|
34
|
+
export declare function orderEntries(entries: readonly WorkspaceDirectoryEntry[]): WorkspaceDirectoryEntry[];
|
|
35
|
+
/**
|
|
36
|
+
* Say why a directory could not be listed, in terms of the directory.
|
|
37
|
+
* @param t - namespace-bound translate.
|
|
38
|
+
* @param failure - the settled Remote failure.
|
|
39
|
+
* @returns the line to show under the directory.
|
|
40
|
+
*/
|
|
41
|
+
export declare function failureLine(t: TranslateNS<'sidebarFiles'>, failure: RemoteFailure): string;
|
|
42
|
+
/**
|
|
43
|
+
* The additional-root tree's body: the selected root and whatever the reader
|
|
44
|
+
* has opened under it — the upstream body's scroll, seed, and render logic
|
|
45
|
+
* verbatim, with the root arriving as a prop instead of `useSessions(cwd)`.
|
|
46
|
+
*/
|
|
47
|
+
export declare function FilesTree({ tabId, sessionId, root, signal, useStore, actions, start, toggle, onOpen, t, }: FilesTreeProps): ReactNode;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The plugin-owned wiring around the verbatim files-tree fork.
|
|
3
|
+
*
|
|
4
|
+
* Upstream wires its store and face inside `apply` over `ctx.remote`, and the
|
|
5
|
+
* slot framework resolves the session and hands the face its bound actions.
|
|
6
|
+
* This plugin registers onto `sidebar.right.tab.files.actions` — a
|
|
7
|
+
* session-scoped list slot — so the same `store`/`inject` declaration gives
|
|
8
|
+
* the fork the same props; what differs is only *which* remote and *which*
|
|
9
|
+
* open-route the fork sees:
|
|
10
|
+
*
|
|
11
|
+
* - `remote` is an optional service here (upstream injects it unconditionally),
|
|
12
|
+
* so {@link lazyFilesRemote} resolves `remote` / `remote.workspaceFiles` per
|
|
13
|
+
* call through `ctx.get` — the inject-free lookup, since a declaration would
|
|
14
|
+
* gate this whole row on an optional service — and folds absence into the
|
|
15
|
+
* same `gateway/internal` failure a carrier error would produce;
|
|
16
|
+
* - opening a file goes through `sidebarRight.openResourceIn`, bound with the
|
|
17
|
+
* session inside the `inject` factory so the component never resolves one;
|
|
18
|
+
* - the primitives the forked rows spell are runtime-probed (pixel-era
|
|
19
|
+
* manifests lack the `*Regular` names), and the gate's `false` means the
|
|
20
|
+
* caller simply never mounts the tree — upstream's own absence shape.
|
|
21
|
+
*
|
|
22
|
+
* Everything here is ours; nothing in this file needs upstream sync.
|
|
23
|
+
*
|
|
24
|
+
* @module @dsh-electron/dsh-plugin-multi-root-workspace/client/files-tree/adapter
|
|
25
|
+
*/
|
|
26
|
+
import type { Context as ClientContext } from '@deepseek-ai/cordis';
|
|
27
|
+
import type { ClientRemote } from '@deepseek-ai/dsh-api-remotes/client';
|
|
28
|
+
import type { BoundActions } from '@deepseek-ai/dsh-client-store';
|
|
29
|
+
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
30
|
+
import { type FilesInjected } from './face.ts';
|
|
31
|
+
import { createFilesStore } from './store.ts';
|
|
32
|
+
import { filesTreeSurfaceSupported } from './primitives.ts';
|
|
33
|
+
import type { FilesTreeProps } from './FilesTree.tsx';
|
|
34
|
+
import { NS } from './locales.ts';
|
|
35
|
+
/** The inject face the registration composes onto the occupant's props. */
|
|
36
|
+
export interface FilesTreeInjected {
|
|
37
|
+
/** The verbatim files face (list/watch bookkeeping included). */
|
|
38
|
+
readonly face: FilesInjected;
|
|
39
|
+
/** Open one `dsh-resource://file` address in the host viewer. */
|
|
40
|
+
readonly openFile: (address: string) => void;
|
|
41
|
+
}
|
|
42
|
+
/** Everything `FilesTree` needs that the slot framework does not synthesize. */
|
|
43
|
+
export interface FilesTreeSurface {
|
|
44
|
+
readonly sessionId: SessionId;
|
|
45
|
+
readonly useStore: FilesTreeProps['useStore'];
|
|
46
|
+
readonly actions: FilesTreeProps['actions'];
|
|
47
|
+
readonly face: FilesInjected;
|
|
48
|
+
readonly openFile: (address: string) => void;
|
|
49
|
+
readonly t: FilesTreeProps['t'];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* A `ClientRemote` stand-in that resolves the real service per call.
|
|
53
|
+
*
|
|
54
|
+
* The verbatim `createList`/`createWatch` hold one remote face for the
|
|
55
|
+
* session's life; resolving lazily keeps the fork correct on compositions
|
|
56
|
+
* that acquire `remote` after this plugin activates, and a composition that
|
|
57
|
+
* never acquires it still answers every call — with the same failure kind a
|
|
58
|
+
* dead carrier returns — instead of crashing the slot. Every method upholds
|
|
59
|
+
* the Remote contract the face relies on: `list` resolves a `RemoteResult`
|
|
60
|
+
* and never rejects; `changes`/`$stream` surface synchronous lookup failures
|
|
61
|
+
* through the stream's own failure path.
|
|
62
|
+
*/
|
|
63
|
+
export declare function lazyFilesRemote(ctx: ClientContext): ClientRemote;
|
|
64
|
+
/**
|
|
65
|
+
* The registration's `store`/`inject` declarations, built once in `apply`.
|
|
66
|
+
*
|
|
67
|
+
* The inject face nests the verbatim files face under `face` and adds the
|
|
68
|
+
* session-bound `openFile` — keeping `FilesInjected` untouched means the
|
|
69
|
+
* fork's call sites and the sync diff stay upstream-shaped.
|
|
70
|
+
* @param ctx - the client root context (the remote/sidebarRight lookups).
|
|
71
|
+
* @returns the store handle and the inject factory for `register`.
|
|
72
|
+
*/
|
|
73
|
+
export declare function createFilesTreeSeats(ctx: ClientContext): {
|
|
74
|
+
readonly store: ReturnType<typeof createFilesStore>;
|
|
75
|
+
readonly inject: (sessionId: SessionId, actions: BoundActions<ReturnType<typeof createFilesStore>>) => FilesTreeInjected;
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Assemble the props `RootSwitcher` portals into `FilesTree`, or `undefined`
|
|
79
|
+
* when this composition cannot render the fork — the caller then keeps the
|
|
80
|
+
* additional-root rows out of the menu entirely, which is the same absence
|
|
81
|
+
* upstream produces when its own tree cannot draw.
|
|
82
|
+
* @param props - the occupant's composed props (store, inject, session shares).
|
|
83
|
+
* @param t - the `sidebarFiles`-bound translate.
|
|
84
|
+
* @returns the stable surface, or `undefined` when the fork cannot mount.
|
|
85
|
+
*/
|
|
86
|
+
export declare function createFilesTreeSurface(props: {
|
|
87
|
+
readonly sessionId: SessionId;
|
|
88
|
+
readonly useStore: FilesTreeSurface['useStore'];
|
|
89
|
+
readonly actions: FilesTreeSurface['actions'];
|
|
90
|
+
readonly face: FilesInjected | undefined;
|
|
91
|
+
readonly openFile: ((address: string) => void) | undefined;
|
|
92
|
+
}, t: FilesTreeProps['t']): FilesTreeSurface | undefined;
|
|
93
|
+
export { filesTreeSurfaceSupported, NS as FILES_TREE_NS };
|
|
94
|
+
export { FilesTree } from './FilesTree.tsx';
|