@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
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { i as isWindowsNetworkOrDevicePath } from "./windows-runner-contract-CEfmZzu0.js";
|
|
2
|
+
import { statSync } from "node:fs";
|
|
1
3
|
import { isAbsolute, join, sep } from "node:path";
|
|
2
4
|
import { canonicalPath } from "@deepseek-ai/dsh-sandbox";
|
|
3
|
-
import { statSync } from "node:fs";
|
|
4
5
|
import { homedir } from "node:os";
|
|
5
6
|
//#region src/roots.ts
|
|
6
7
|
/**
|
|
@@ -27,10 +28,18 @@ import { homedir } from "node:os";
|
|
|
27
28
|
* 6. `duplicate` — another registered root canonicalizes to the same path.
|
|
28
29
|
* 7. `nested` — the candidate lies under a registered root, or contains one.
|
|
29
30
|
* Same reasoning as rule 5, applied to the additional roots.
|
|
31
|
+
* 8. `unsupported-path` — on Windows, a network or device path (`\\server`,
|
|
32
|
+
* `\\?\…`, `\\.\…`): the ACL confinement cannot be shown to cover it, so it
|
|
33
|
+
* is rejected lexically before any filesystem touch.
|
|
30
34
|
*
|
|
31
35
|
* The stored side of the same rules lives in {@link classifyStoredRoots}, which
|
|
32
36
|
* must never throw: a registration that stopped being usable is REPORTED, not
|
|
33
|
-
* deleted, so the operator can see it and remove it deliberately.
|
|
37
|
+
* deleted, so the operator can see it and remove it deliberately. It runs in
|
|
38
|
+
* two passes — every record's own structural trust and availability first,
|
|
39
|
+
* then overlap between the RECORDED boundaries of all structurally sound
|
|
40
|
+
* records — so the answer never depends on registry order, and a pair of
|
|
41
|
+
* records whose grants collide (`membership-conflict`) keeps its trustworthy
|
|
42
|
+
* negative boundary even while its membership is withheld.
|
|
34
43
|
*
|
|
35
44
|
* @module @dsh-electron/dsh-plugin-multi-root-workspace/roots
|
|
36
45
|
*/
|
|
@@ -117,7 +126,10 @@ function validateRootCandidate(raw, context) {
|
|
|
117
126
|
const expanded = expandRootInput(raw, context.home);
|
|
118
127
|
if (expanded === "") throw new RootValidationError("not-absolute", "a root path is required");
|
|
119
128
|
if (!isAbsolute(expanded)) throw new RootValidationError("not-absolute", `"${expanded}" is not an absolute path; give an absolute directory or one starting with "~/"`, { reference: expanded });
|
|
129
|
+
const platform = context.platform ?? process.platform;
|
|
130
|
+
if (platform === "win32" && isWindowsNetworkOrDevicePath(expanded)) throw unsupportedPath(expanded);
|
|
120
131
|
const canonical = canonicalRoot(expanded);
|
|
132
|
+
if (platform === "win32" && isWindowsNetworkOrDevicePath(canonical)) throw unsupportedPath(expanded);
|
|
121
133
|
assertDirectory(canonical, expanded);
|
|
122
134
|
const primaryRoot = canonicalRoot(context.primaryRoot);
|
|
123
135
|
if (samePath(canonical, primaryRoot)) throw new RootValidationError("equals-primary", `"${canonical}" is this session's workspace root and is already writable`, {
|
|
@@ -144,6 +156,10 @@ function validateRootCandidate(raw, context) {
|
|
|
144
156
|
}
|
|
145
157
|
return canonical;
|
|
146
158
|
}
|
|
159
|
+
/** The `unsupported-path` refusal, shared by the input and canonical checks. */
|
|
160
|
+
function unsupportedPath(asTyped) {
|
|
161
|
+
return new RootValidationError("unsupported-path", `"${asTyped}" is a network or device path, which Windows ACL confinement cannot cover`, { reference: asTyped });
|
|
162
|
+
}
|
|
147
163
|
/** Assert that a canonical path is an existing directory. */
|
|
148
164
|
function assertDirectory(canonical, asTyped) {
|
|
149
165
|
let stats;
|
|
@@ -164,19 +180,71 @@ function assertDirectory(canonical, asTyped) {
|
|
|
164
180
|
* `invalid` (kept, unusable).
|
|
165
181
|
* @param primaryRoot - the session workspace root the records belong to.
|
|
166
182
|
* @param records - the stored records, in registry order.
|
|
183
|
+
* @param platform - the platform the rules run for; defaults to `process.platform`.
|
|
167
184
|
* @returns one status per record, in the same order.
|
|
168
185
|
*/
|
|
169
|
-
function classifyStoredRoots(primaryRoot, records) {
|
|
186
|
+
function classifyStoredRoots(primaryRoot, records, platform = process.platform) {
|
|
170
187
|
const primary = canonicalRoot(primaryRoot);
|
|
171
|
-
const claimed = [];
|
|
172
188
|
const duplicated = duplicatedIds(records);
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
189
|
+
return markMembershipConflicts(records.map((record, index) => classifyStoredRoot(record, primary, duplicated.has(record.id), index, platform)));
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Pass two of {@link classifyStoredRoots}: every pair of structurally sound
|
|
193
|
+
* records whose RECORDED boundaries overlap is a membership conflict, and
|
|
194
|
+
* BOTH are unusable — which side "won" must never depend on registry order
|
|
195
|
+
* (a reorder is cosmetic and cannot revoke a deny boundary). Missing and
|
|
196
|
+
* redirected records participate too: withheld roots carry no grant, but
|
|
197
|
+
* their recorded boundaries still describe restrictions the operator has not
|
|
198
|
+
* removed.
|
|
199
|
+
*
|
|
200
|
+
* The comparison is lexical over the recorded grant via
|
|
201
|
+
* {@link isCanonicallyUnder}/{@link samePath} — never the current resolution,
|
|
202
|
+
* which a replaced directory can steer. The conflict set is built from the
|
|
203
|
+
* ORIGINAL first-pass statuses, so a third overlapping record is marked even
|
|
204
|
+
* when the pair found first was already doomed.
|
|
205
|
+
*/
|
|
206
|
+
function markMembershipConflicts(statuses) {
|
|
207
|
+
const boundaries = statuses.map((status) => status.state === "invalid" ? void 0 : singleGrantOf(status));
|
|
208
|
+
const conflicts = /* @__PURE__ */ new Map();
|
|
209
|
+
for (let index = 0; index < boundaries.length; index += 1) {
|
|
210
|
+
const boundary = boundaries[index];
|
|
211
|
+
if (boundary === void 0) continue;
|
|
212
|
+
for (let other = index + 1; other < boundaries.length; other += 1) {
|
|
213
|
+
const otherBoundary = boundaries[other];
|
|
214
|
+
if (otherBoundary === void 0) continue;
|
|
215
|
+
if (samePath(boundary, otherBoundary) || isCanonicallyUnder(boundary, otherBoundary) || isCanonicallyUnder(otherBoundary, boundary)) {
|
|
216
|
+
conflicts.set(index, [...conflicts.get(index) ?? [], otherBoundary]);
|
|
217
|
+
conflicts.set(other, [...conflicts.get(other) ?? [], boundary]);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
178
220
|
}
|
|
179
|
-
return statuses;
|
|
221
|
+
if (conflicts.size === 0) return [...statuses];
|
|
222
|
+
return statuses.map((status, index) => {
|
|
223
|
+
const others = conflicts.get(index);
|
|
224
|
+
if (others === void 0) return status;
|
|
225
|
+
const boundary = boundaries[index];
|
|
226
|
+
const partners = others.map((other) => `"${other}"`).join(", ");
|
|
227
|
+
return {
|
|
228
|
+
...status,
|
|
229
|
+
state: "invalid",
|
|
230
|
+
invalidReason: "membership-conflict",
|
|
231
|
+
detail: `the recorded grant "${boundary}" overlaps the recorded grant ${partners} — neither membership is granted until one registration is removed, and any recorded read-only restriction remains in force`
|
|
232
|
+
};
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* The single recorded grant a record or status carries — `undefined` unless
|
|
237
|
+
* EXACTLY one grant field holds a non-empty string. Shared by the conflict
|
|
238
|
+
* pass and {@link reservedRootBoundary}; the stored decoder is the source of
|
|
239
|
+
* the access level, so this never cross-checks the field against `access`.
|
|
240
|
+
*/
|
|
241
|
+
function singleGrantOf(record) {
|
|
242
|
+
const grants = [
|
|
243
|
+
record.recordedPath,
|
|
244
|
+
record.readOnlyRecordedPath,
|
|
245
|
+
record.readWriteRecordedPath
|
|
246
|
+
].filter((grant) => typeof grant === "string" && grant !== "");
|
|
247
|
+
return grants.length === 1 ? grants[0] : void 0;
|
|
180
248
|
}
|
|
181
249
|
/**
|
|
182
250
|
* The ids that more than one record claims. Computed for the whole list up
|
|
@@ -192,15 +260,18 @@ function duplicatedIds(records) {
|
|
|
192
260
|
return duplicated;
|
|
193
261
|
}
|
|
194
262
|
/**
|
|
195
|
-
* Judge one stored record
|
|
196
|
-
*
|
|
263
|
+
* Judge one stored record's structural trust and current availability —
|
|
264
|
+
* WITHOUT looking at any other record (record-vs-record overlap is the second
|
|
265
|
+
* pass of {@link classifyStoredRoots}).
|
|
197
266
|
*
|
|
198
267
|
* Identity and the recorded directory are checked BEFORE the current
|
|
199
268
|
* resolution is used for anything: a record whose path now resolves elsewhere
|
|
200
269
|
* must not have its new (attacker-influenced) target participate in the
|
|
201
|
-
* overlap rules.
|
|
270
|
+
* overlap rules. The primary-root rules therefore judge the RECORDED
|
|
271
|
+
* boundary — the directory the operator was granted — not what `path`
|
|
272
|
+
* resolves to today.
|
|
202
273
|
*/
|
|
203
|
-
function classifyStoredRoot(record, primary,
|
|
274
|
+
function classifyStoredRoot(record, primary, duplicatedId, position, platform) {
|
|
204
275
|
const invalid = (detail) => ({
|
|
205
276
|
...record,
|
|
206
277
|
state: "invalid",
|
|
@@ -210,18 +281,24 @@ function classifyStoredRoot(record, primary, claimed, duplicatedId, position) {
|
|
|
210
281
|
if (typeof record.id !== "string" || record.id === "") return invalid("the record has no id");
|
|
211
282
|
if (duplicatedId) return invalid(`the id "${record.id}" is used by more than one record (entry ${position + 1})`);
|
|
212
283
|
if (!isAbsolute(record.path)) return invalid(`"${record.path}" is not an absolute path`);
|
|
213
|
-
const
|
|
284
|
+
const grants = [
|
|
285
|
+
record.recordedPath,
|
|
286
|
+
record.readOnlyRecordedPath,
|
|
287
|
+
record.readWriteRecordedPath
|
|
288
|
+
].filter((grant) => typeof grant === "string" && grant !== "");
|
|
289
|
+
if (grants.length > 1) return invalid("the record declares more than one grant (ambiguous access)");
|
|
290
|
+
const recorded = grants[0];
|
|
214
291
|
if (typeof recorded !== "string" || recorded === "") return invalid("the record does not say which directory it was granted for");
|
|
292
|
+
if (!isAbsolute(recorded)) return invalid(`the recorded grant "${recorded}" is not an absolute path`);
|
|
293
|
+
if (platform === "win32" && (isWindowsNetworkOrDevicePath(record.path) || isWindowsNetworkOrDevicePath(recorded))) return invalid(`"${record.path}" is a network or device path, which Windows ACL confinement cannot cover`);
|
|
294
|
+
if (samePath(recorded, primary)) return invalid(`"${recorded}" is this session's workspace root`);
|
|
295
|
+
if (isCanonicallyUnder(recorded, primary) || isCanonicallyUnder(primary, recorded)) return invalid(`"${recorded}" overlaps this session's workspace root`);
|
|
215
296
|
const canonical = canonicalRoot(record.path);
|
|
216
297
|
if (!samePath(canonical, recorded)) return {
|
|
217
298
|
...record,
|
|
218
299
|
state: "redirected",
|
|
219
300
|
detail: `the path now resolves to "${canonical}" instead of the registered "${recorded}"`
|
|
220
301
|
};
|
|
221
|
-
if (samePath(canonical, primary)) return invalid(`"${canonical}" is this session's workspace root`);
|
|
222
|
-
if (isCanonicallyUnder(canonical, primary) || isCanonicallyUnder(primary, canonical)) return invalid(`"${canonical}" overlaps this session's workspace root`);
|
|
223
|
-
if (claimed.some((existing) => samePath(existing, canonical))) return invalid(`"${canonical}" is registered twice`);
|
|
224
|
-
for (const existing of claimed) if (isCanonicallyUnder(canonical, existing) || isCanonicallyUnder(existing, canonical)) return invalid(`"${canonical}" overlaps the registered root "${existing}"`);
|
|
225
302
|
let isDirectory = false;
|
|
226
303
|
try {
|
|
227
304
|
isDirectory = statSync(canonical).isDirectory();
|
|
@@ -249,6 +326,26 @@ function availableRoots(statuses) {
|
|
|
249
326
|
return statuses.filter((status) => status.state === "available").map((status) => status.path);
|
|
250
327
|
}
|
|
251
328
|
/**
|
|
329
|
+
* The recorded boundary one status still RESERVES: the grant it was
|
|
330
|
+
* registered for, which a new or revived registration may not overlap merely
|
|
331
|
+
* because the root is currently unusable.
|
|
332
|
+
*
|
|
333
|
+
* `available`, `missing`, `redirected`, and membership-conflict statuses all
|
|
334
|
+
* carry an intact, trustworthy recorded grant; every other `invalid` verdict
|
|
335
|
+
* means the grant could not be parsed at all, so it reserves — and denies —
|
|
336
|
+
* nothing. This is not an independent authorization table: it derives the
|
|
337
|
+
* boundary from the status list {@link classifyStoredRoots} produced, with
|
|
338
|
+
* the same grant-extraction semantics, so its answer can never disagree with
|
|
339
|
+
* classification.
|
|
340
|
+
* @param status - one classified registration.
|
|
341
|
+
* @returns the absolute recorded boundary, or `undefined` when there is none.
|
|
342
|
+
*/
|
|
343
|
+
function reservedRootBoundary(status) {
|
|
344
|
+
if (status.state === "invalid" && status.invalidReason !== "membership-conflict") return void 0;
|
|
345
|
+
const boundary = singleGrantOf(status);
|
|
346
|
+
return boundary !== void 0 && isAbsolute(boundary) ? boundary : void 0;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
252
349
|
* Pair every status with the 1-based ordinal the surfaces accept for it. The
|
|
253
350
|
* ordinal belongs to the POSITION, so it stays correct even for records whose
|
|
254
351
|
* id is duplicated or missing — which is exactly the case where a positional
|
|
@@ -342,4 +439,4 @@ function describeRef(ref) {
|
|
|
342
439
|
}
|
|
343
440
|
}
|
|
344
441
|
//#endregion
|
|
345
|
-
export { classifyStoredRoots as a, indexedStatuses as c,
|
|
442
|
+
export { classifyStoredRoots as a, indexedStatuses as c, reservedRootBoundary as d, resolveRootIndex as f, canonicalRoot as i, isCanonicallyUnder as l, validateRootCandidate as m, additionalRootId as n, entryRootRef as o, resolveRootRef as p, availableRoots as r, expandRootInput as s, RootValidationError as t, removeStatusAt as u };
|