@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
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
//#region src/contract.ts
|
|
3
|
+
/**
|
|
4
|
+
* The wire contract between the browser panel and this plugin's host half.
|
|
5
|
+
*
|
|
6
|
+
* It is deliberately transport-agnostic plain data: the host registers one
|
|
7
|
+
* Connection RPC channel (see `command.ts`) and the client bundle inlines this
|
|
8
|
+
* module, so both sides agree on endpoint names and payload shapes without
|
|
9
|
+
* sharing any runtime identity. Error codes are the stable half of the
|
|
10
|
+
* contract; `message` is only a fallback the panel does not have to show.
|
|
11
|
+
*
|
|
12
|
+
* Two properties this module owns:
|
|
13
|
+
*
|
|
14
|
+
* - **One shape per endpoint.** {@link PanelResponseMap} is the single mapping
|
|
15
|
+
* from endpoint to response type, so the panel cannot silently decode one
|
|
16
|
+
* endpoint's answer as another's. `reveal` answers with
|
|
17
|
+
* {@link RevealedView}; root-management endpoints answer with
|
|
18
|
+
* {@link RootsView}.
|
|
19
|
+
* The host builds those answers in `command.ts` and the
|
|
20
|
+
* panel parses them here — one definition, two consumers.
|
|
21
|
+
* - **Runtime validation at the boundary.** The payload arrives from a browser
|
|
22
|
+
* and the answer arrives from the host; neither is trusted to match the
|
|
23
|
+
* TypeScript types. {@link parsePanelCall} and the `parse*View` functions
|
|
24
|
+
* check the shapes and report structural failures as a message, so a
|
|
25
|
+
* mismatch becomes a visible, localized error instead of a crash or a
|
|
26
|
+
* silently wrong render.
|
|
27
|
+
*
|
|
28
|
+
* Nothing here may import `node:*` or `@deepseek-ai/*`: this module is inlined
|
|
29
|
+
* into the browser bundle.
|
|
30
|
+
*
|
|
31
|
+
* @module @dsh-electron/dsh-plugin-multi-root-workspace/contract
|
|
32
|
+
*/
|
|
33
|
+
/** The absolute logical channel both halves speak over. */
|
|
34
|
+
const PANEL_CHANNEL = "/multi-root-workspace";
|
|
35
|
+
/** Every endpoint name, for exhaustive iteration in tests and callers. */
|
|
36
|
+
const PANEL_ENDPOINTS = [
|
|
37
|
+
"list",
|
|
38
|
+
"add",
|
|
39
|
+
"remove",
|
|
40
|
+
"alias",
|
|
41
|
+
"move",
|
|
42
|
+
"setAccess",
|
|
43
|
+
"reveal"
|
|
44
|
+
];
|
|
45
|
+
/** The shared nine-cell rule; tool dispatch may subsequently narrow writes. */
|
|
46
|
+
function effectiveRootAccess(access, mode) {
|
|
47
|
+
if (access !== "inherit") return access;
|
|
48
|
+
return mode === "danger-full-access" ? "full-access" : mode === "workspace-write" ? "read-write" : "read-only";
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The request body on the wire. The endpoint travels as the RPC method, not as
|
|
52
|
+
* a field, so this schema covers the fields only; the host pairs it with the
|
|
53
|
+
* method the channel dispatched (and rejects the pair if the body contradicts
|
|
54
|
+
* it). Unknown keys are rejected rather than ignored: a client sending a field
|
|
55
|
+
* this host does not understand is out of contract, and silently dropping it
|
|
56
|
+
* would hide the drift.
|
|
57
|
+
*/
|
|
58
|
+
const panelRequestSchema = z.object({
|
|
59
|
+
sessionId: z.string().min(1),
|
|
60
|
+
entry: z.object({
|
|
61
|
+
ordinal: z.number().int().positive(),
|
|
62
|
+
id: z.string(),
|
|
63
|
+
path: z.string(),
|
|
64
|
+
addedAt: z.string()
|
|
65
|
+
}).strict().optional(),
|
|
66
|
+
id: z.string().optional(),
|
|
67
|
+
path: z.string().optional(),
|
|
68
|
+
alias: z.string().optional(),
|
|
69
|
+
access: z.enum([
|
|
70
|
+
"inherit",
|
|
71
|
+
"read-write",
|
|
72
|
+
"read-only"
|
|
73
|
+
]).optional(),
|
|
74
|
+
beforeEntry: z.object({
|
|
75
|
+
ordinal: z.number().int().positive(),
|
|
76
|
+
id: z.string(),
|
|
77
|
+
path: z.string(),
|
|
78
|
+
addedAt: z.string()
|
|
79
|
+
}).strict().optional(),
|
|
80
|
+
beforeId: z.string().optional()
|
|
81
|
+
}).strict();
|
|
82
|
+
/**
|
|
83
|
+
* One root as it travels over the channel. Deliberately NOT strict, in both
|
|
84
|
+
* directions: a newer host that adds a field must not brick an older panel, and
|
|
85
|
+
* the field that matters is the state it reports.
|
|
86
|
+
*/
|
|
87
|
+
const rootViewSchema = z.object({
|
|
88
|
+
ordinal: z.number().int().positive(),
|
|
89
|
+
id: z.string(),
|
|
90
|
+
path: z.string(),
|
|
91
|
+
alias: z.string().optional(),
|
|
92
|
+
addedAt: z.string(),
|
|
93
|
+
state: z.enum([
|
|
94
|
+
"available",
|
|
95
|
+
"missing",
|
|
96
|
+
"redirected",
|
|
97
|
+
"invalid"
|
|
98
|
+
]),
|
|
99
|
+
access: z.enum([
|
|
100
|
+
"inherit",
|
|
101
|
+
"read-write",
|
|
102
|
+
"read-only"
|
|
103
|
+
]).optional(),
|
|
104
|
+
detail: z.string().optional(),
|
|
105
|
+
effectiveAccess: z.enum([
|
|
106
|
+
"read-only",
|
|
107
|
+
"read-write",
|
|
108
|
+
"full-access",
|
|
109
|
+
"unavailable"
|
|
110
|
+
]).optional()
|
|
111
|
+
});
|
|
112
|
+
const rootsViewSchema = z.object({
|
|
113
|
+
primaryRoot: z.string(),
|
|
114
|
+
primaryName: z.string().optional(),
|
|
115
|
+
effectiveCwd: z.string().optional(),
|
|
116
|
+
mode: z.enum([
|
|
117
|
+
"read-only",
|
|
118
|
+
"workspace-write",
|
|
119
|
+
"danger-full-access"
|
|
120
|
+
]).optional(),
|
|
121
|
+
cappedTools: z.array(z.string()).optional(),
|
|
122
|
+
toolAccessInvalid: z.boolean().optional(),
|
|
123
|
+
roots: z.array(rootViewSchema),
|
|
124
|
+
limit: z.number().int().nonnegative().optional(),
|
|
125
|
+
count: z.number().int().nonnegative().optional(),
|
|
126
|
+
unavailable: z.string().optional()
|
|
127
|
+
});
|
|
128
|
+
const revealedViewSchema = z.object({ revealed: z.string() });
|
|
129
|
+
z.object({
|
|
130
|
+
code: z.string(),
|
|
131
|
+
message: z.string(),
|
|
132
|
+
details: z.unknown().optional()
|
|
133
|
+
});
|
|
134
|
+
/**
|
|
135
|
+
* Validate the payload the host received for one endpoint.
|
|
136
|
+
* @param endpoint - the endpoint the channel dispatched (the RPC method).
|
|
137
|
+
* @param payload - the raw channel payload (the request body).
|
|
138
|
+
* @returns the call the host may act on, or the reason it is not one.
|
|
139
|
+
*/
|
|
140
|
+
function parsePanelCall(endpoint, payload) {
|
|
141
|
+
if (!PANEL_ENDPOINTS.includes(endpoint)) return {
|
|
142
|
+
ok: false,
|
|
143
|
+
message: `unknown endpoint "${endpoint}"`
|
|
144
|
+
};
|
|
145
|
+
const parsed = panelRequestSchema.safeParse(payload ?? {});
|
|
146
|
+
if (!parsed.success) return {
|
|
147
|
+
ok: false,
|
|
148
|
+
message: `the request body is invalid: ${firstIssue(parsed.error)}`
|
|
149
|
+
};
|
|
150
|
+
return {
|
|
151
|
+
ok: true,
|
|
152
|
+
value: narrowCall(endpoint, parsed.data)
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Validate a `RootsView` when it arrives.
|
|
157
|
+
* @param value - the raw endpoint value.
|
|
158
|
+
* @returns the view, or the reason it is not one.
|
|
159
|
+
*/
|
|
160
|
+
function parseRootsView(value) {
|
|
161
|
+
const parsed = rootsViewSchema.safeParse(value);
|
|
162
|
+
if (!parsed.success) return {
|
|
163
|
+
ok: false,
|
|
164
|
+
message: `the answer is not a roots view: ${firstIssue(parsed.error)}`
|
|
165
|
+
};
|
|
166
|
+
const view = parsed.data;
|
|
167
|
+
return {
|
|
168
|
+
ok: true,
|
|
169
|
+
value: {
|
|
170
|
+
primaryRoot: view.primaryRoot,
|
|
171
|
+
...view.primaryName === void 0 ? {} : { primaryName: view.primaryName },
|
|
172
|
+
...view.effectiveCwd === void 0 ? {} : { effectiveCwd: view.effectiveCwd },
|
|
173
|
+
...view.mode === void 0 ? {} : { mode: view.mode },
|
|
174
|
+
...view.cappedTools === void 0 ? {} : { cappedTools: view.cappedTools },
|
|
175
|
+
...view.toolAccessInvalid === void 0 ? {} : { toolAccessInvalid: view.toolAccessInvalid },
|
|
176
|
+
roots: view.roots.map(narrowRootView),
|
|
177
|
+
limit: view.limit ?? Number.MAX_SAFE_INTEGER,
|
|
178
|
+
count: view.count ?? view.roots.length,
|
|
179
|
+
...view.unavailable === void 0 ? {} : { unavailable: view.unavailable }
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Validate a `RevealedView` when it arrives.
|
|
185
|
+
* @param value - the raw endpoint value.
|
|
186
|
+
* @returns the view, or the reason it is not one.
|
|
187
|
+
*/
|
|
188
|
+
function parseRevealedView(value) {
|
|
189
|
+
const parsed = revealedViewSchema.safeParse(value);
|
|
190
|
+
if (!parsed.success) return {
|
|
191
|
+
ok: false,
|
|
192
|
+
message: `the answer is not a reveal result: ${firstIssue(parsed.error)}`
|
|
193
|
+
};
|
|
194
|
+
return {
|
|
195
|
+
ok: true,
|
|
196
|
+
value: parsed.data
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Rebuild a parsed call as the declared type, keeping absent optional fields
|
|
201
|
+
* ABSENT (`exactOptionalPropertyTypes` distinguishes "absent" from "undefined",
|
|
202
|
+
* and the host's own code reads the two the same way the panel sent them).
|
|
203
|
+
*/
|
|
204
|
+
function narrowCall(endpoint, value) {
|
|
205
|
+
return {
|
|
206
|
+
endpoint,
|
|
207
|
+
sessionId: value.sessionId,
|
|
208
|
+
...value.entry === void 0 ? {} : { entry: value.entry },
|
|
209
|
+
...value.id === void 0 ? {} : { id: value.id },
|
|
210
|
+
...value.path === void 0 ? {} : { path: value.path },
|
|
211
|
+
...value.alias === void 0 ? {} : { alias: value.alias },
|
|
212
|
+
...value.access === void 0 ? {} : { access: value.access },
|
|
213
|
+
...value.beforeEntry === void 0 ? {} : { beforeEntry: value.beforeEntry },
|
|
214
|
+
...value.beforeId === void 0 ? {} : { beforeId: value.beforeId }
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
/** Rebuild one parsed root as the declared `RootView`. */
|
|
218
|
+
function narrowRootView(value) {
|
|
219
|
+
return {
|
|
220
|
+
ordinal: value.ordinal,
|
|
221
|
+
id: value.id,
|
|
222
|
+
path: value.path,
|
|
223
|
+
addedAt: value.addedAt,
|
|
224
|
+
state: value.state,
|
|
225
|
+
access: value.access ?? "inherit",
|
|
226
|
+
...value.effectiveAccess === void 0 ? {} : { effectiveAccess: value.effectiveAccess },
|
|
227
|
+
...value.alias === void 0 ? {} : { alias: value.alias },
|
|
228
|
+
...value.detail === void 0 ? {} : { detail: value.detail }
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
/** Render the first zod issue as one short sentence. */
|
|
232
|
+
function firstIssue(error) {
|
|
233
|
+
const issue = error.issues[0];
|
|
234
|
+
if (issue === void 0) return "invalid";
|
|
235
|
+
return `${issue.path.length === 0 ? "" : `${issue.path.join(".")}: `}${issue.message}`;
|
|
236
|
+
}
|
|
237
|
+
//#endregion
|
|
238
|
+
export { parseRevealedView as a, parsePanelCall as i, PANEL_ENDPOINTS as n, parseRootsView as o, effectiveRootAccess as r, PANEL_CHANNEL as t };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { brandMultiRootProvider } from "./providers.js";
|
|
1
2
|
import { dirname, sep } from "node:path";
|
|
2
3
|
import { writableRoots } from "@deepseek-ai/dsh-sandbox";
|
|
3
4
|
import { FsError } from "@deepseek-ai/dsh-fs";
|
|
@@ -64,14 +65,11 @@ async function isPathUnder(path, root, caseSensitive = process.platform !== "win
|
|
|
64
65
|
* roots.
|
|
65
66
|
*/
|
|
66
67
|
var MultiRootFileSystem = class extends LocalFileSystem {
|
|
67
|
-
static inject = [
|
|
68
|
-
"multiRootCompat",
|
|
69
|
-
"sandboxPolicy",
|
|
70
|
-
"multiRootScope"
|
|
71
|
-
];
|
|
68
|
+
static inject = ["sandboxPolicy", "multiRootScope"];
|
|
72
69
|
defaultMode;
|
|
73
70
|
constructor(ctx, config) {
|
|
74
71
|
super(ctx, config);
|
|
72
|
+
brandMultiRootProvider(this, "fs");
|
|
75
73
|
this.defaultMode = ctx.sandboxPolicy.defaultMode;
|
|
76
74
|
}
|
|
77
75
|
/** The deployment default mode — the capability fact the tool layer reads to advertise escalation. */
|
|
@@ -107,47 +105,81 @@ var MultiRootFileSystem = class extends LocalFileSystem {
|
|
|
107
105
|
return super.editText(await this.checkedTarget(target, sandboxPolicy), edit, expected, signal);
|
|
108
106
|
}
|
|
109
107
|
/**
|
|
110
|
-
* The writable roots of one
|
|
111
|
-
*
|
|
112
|
-
* widened by the scope's
|
|
108
|
+
* The writable roots of one confined call: the upstream derivation (primary
|
|
109
|
+
* root plus the platform temp areas, canonical and deduplicated — empty
|
|
110
|
+
* under `read-only`) widened by the scope's EFFECTIVE writable members.
|
|
111
|
+
* Under `read-only` that is exactly the fixed `read-write` roots (the tool
|
|
112
|
+
* cap may already have emptied the scope's list), so an explicit grant
|
|
113
|
+
* survives a globally read-only session. An unavailable registry authority
|
|
114
|
+
* never contributes members: a process that cannot read the store grants
|
|
115
|
+
* only the primary root (and no shared temp grant).
|
|
113
116
|
* @param policy - the per-call policy.
|
|
114
117
|
* @param scope - the scope resolved once for this call; both the fence and
|
|
115
118
|
* the denial message below share it instead of re-resolving.
|
|
116
|
-
* @returns the canonical writable roots; empty
|
|
119
|
+
* @returns the canonical writable roots; possibly empty.
|
|
117
120
|
*/
|
|
118
121
|
rootsFor(policy, scope) {
|
|
122
|
+
if (scope.authority === "unavailable") return policy.mode === "read-only" ? [] : [scope.primaryRoot];
|
|
119
123
|
const roots = writableRoots(policy);
|
|
120
|
-
|
|
121
|
-
for (const root of scope.additionalRoots) if (!roots.includes(root)) roots.push(root);
|
|
124
|
+
for (const root of scope.writableRoots) if (!roots.includes(root)) roots.push(root);
|
|
122
125
|
return roots;
|
|
123
126
|
}
|
|
124
127
|
/**
|
|
125
|
-
* Enforce the per-call policy against `target` and return the EXACT target
|
|
126
|
-
* mutation must use, so the checked identity is the mutated one (no
|
|
127
|
-
* check-here-write-there TOCTOU).
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
128
|
+
* Enforce the per-call policy against `target` and return the EXACT target
|
|
129
|
+
* the mutation must use, so the checked identity is the mutated one (no
|
|
130
|
+
* check-here-write-there TOCTOU). Deny precedes containment: a root in
|
|
131
|
+
* `scope.deniedRoots` — an explicit `read-only` root, or any member when the
|
|
132
|
+
* call is tool-capped — refuses writes under EVERY mode, including
|
|
133
|
+
* `danger-full-access` and where a broader grant (the platform temp area)
|
|
134
|
+
* would otherwise cover the path. `read-only` then grants only the fixed
|
|
135
|
+
* `read-write` members; `workspace-write` requires containment under the
|
|
136
|
+
* widened writable set.
|
|
137
|
+
*
|
|
138
|
+
* `danger-full-access` is the only mode with a fast path, and only while
|
|
139
|
+
* the deny set is empty: it then returns the caller's target untouched,
|
|
140
|
+
* upstream-exact. Once ANY deny boundary exists the caller's `FsTarget` may
|
|
141
|
+
* be stale — the directory it resolved through can since have been replaced
|
|
142
|
+
* by a symlink into a denied root — so the fence re-resolves
|
|
143
|
+
* `target.displayPath`, checks the FRESH target key, and mutates that fresh
|
|
144
|
+
* target rather than the stale one the caller resolved earlier.
|
|
145
|
+
*
|
|
146
|
+
* One more Full-access rule: while the registry authority is `unavailable`
|
|
147
|
+
* the empty deny set is not trustworthy — the store this process cannot
|
|
148
|
+
* read may hold explicit `read-only` registrations — so the mode degrades
|
|
149
|
+
* to primary-root-only writes (no shared temp or additional-root grants)
|
|
150
|
+
* until the authority returns.
|
|
131
151
|
* @param target - the resolved target about to be mutated.
|
|
132
152
|
* @param sandboxPolicy - the per-call policy; omit for the deployment fallback.
|
|
133
153
|
* @returns the fresh canonical target the mutation must use.
|
|
134
154
|
*/
|
|
135
155
|
async checkedTarget(target, sandboxPolicy) {
|
|
136
156
|
const policy = sandboxPolicy ?? this.ctx.sandboxPolicy.resolve();
|
|
137
|
-
const { mode } = policy;
|
|
138
|
-
if (mode === "danger-full-access") return target;
|
|
139
|
-
if (mode === "read-only") throw new FsError(`cannot write "${target.displayPath}": file access denied under read-only mode`, "FS_SANDBOX_DENIED");
|
|
140
|
-
const fresh = await this.resolve(target.displayPath);
|
|
141
157
|
const scope = this.ctx.multiRootScope.resolve(policy);
|
|
142
|
-
const
|
|
158
|
+
const effectiveMode = scope.authority === "unavailable" && policy.mode === "danger-full-access" ? "workspace-write" : policy.mode;
|
|
159
|
+
if (effectiveMode === "danger-full-access" && scope.deniedRoots.length === 0) return target;
|
|
160
|
+
const fresh = await this.resolve(target.displayPath);
|
|
161
|
+
if (scope.deniedRoots.length > 0) {
|
|
162
|
+
for (const path of scope.deniedRoots) if (await isPathUnder(fresh.targetKey, path)) {
|
|
163
|
+
const root = scope.roots.find((root) => root.path === path);
|
|
164
|
+
const reason = path === scope.protectedHome ? `${path} is the DSH home directory, which is protected under danger-full-access while workspace restrictions apply` : root?.access === "read-only" || root === void 0 ? `${path} is a read-only additional root` : `${path} is an additional root this call may not write`;
|
|
165
|
+
throw new FsError(`cannot write "${target.displayPath}": ${reason}`, "FS_SANDBOX_DENIED");
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (effectiveMode === "danger-full-access") return fresh;
|
|
169
|
+
const effectivePolicy = {
|
|
170
|
+
...policy,
|
|
171
|
+
mode: effectiveMode
|
|
172
|
+
};
|
|
173
|
+
const roots = this.rootsFor(effectivePolicy, scope);
|
|
143
174
|
let contained = false;
|
|
144
175
|
for (const root of roots) if (await isPathUnder(fresh.targetKey, root)) {
|
|
145
176
|
contained = true;
|
|
146
177
|
break;
|
|
147
178
|
}
|
|
148
179
|
if (!contained) {
|
|
149
|
-
|
|
150
|
-
|
|
180
|
+
let denial = `cannot write "${target.displayPath}": file access denied under ${effectiveMode} mode`;
|
|
181
|
+
if (scope.additionalRoots.length > 0) denial += `; allowed roots: ${roots.join(", ")}`;
|
|
182
|
+
throw new FsError(denial, "FS_SANDBOX_DENIED");
|
|
151
183
|
}
|
|
152
184
|
return fresh;
|
|
153
185
|
}
|
package/lib/fs.js
CHANGED
package/lib/index.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { c as
|
|
2
|
-
import { a as composeInstructionMessage, c as createInstructionMessage, l as instructionsApi, r as PLUGIN_SOURCE, t as DEFAULT_MAX_BYTES, u as renderInstructions } from "./instructions-
|
|
3
|
-
import { n as widenConfined, t as MultiRootSandboxProvider } from "./sandbox-
|
|
4
|
-
import { a as classifyStoredRoots, c as indexedStatuses,
|
|
5
|
-
import {
|
|
1
|
+
import { _ as classifyInstallation, c as agentPtyMarkerVerdict, d as HOST_SERVICE_PACKAGES, f as OPTIONAL_CORE_PACKAGES, g as SUPPORTED_DSH_RELEASES, h as RUNTIME_OPTIONAL_PACKAGES, i as assertSupportedInstallation, l as assertAgentPtyMarker, m as RUNNER_ANCHORED_PACKAGES, n as MultiRootCompatService, o as AGENT_PTY_MARKER, p as REQUIRED_CORE_PACKAGES, s as PtyMarkerContractError, t as DshCompatUnsupportedError, u as CORE_PACKAGES, v as compatEnforcement, y as readInstalledVersion } from "./compat-DR2LvmYZ.js";
|
|
2
|
+
import { a as composeInstructionMessage, c as createInstructionMessage, l as instructionsApi, r as PLUGIN_SOURCE, t as DEFAULT_MAX_BYTES, u as renderInstructions } from "./instructions-D19jRxN0.js";
|
|
3
|
+
import { n as widenConfined, t as MultiRootSandboxProvider } from "./sandbox-CVAcT_gy.js";
|
|
4
|
+
import { a as classifyStoredRoots, c as indexedStatuses, i as canonicalRoot, l as isCanonicallyUnder, m as validateRootCandidate, n as additionalRootId, p as resolveRootRef, r as availableRoots, s as expandRootInput, t as RootValidationError, u as removeStatusAt } from "./roots-B-0l8mNG.js";
|
|
5
|
+
import { MULTI_ROOT_PROVIDER_BRAND, MultiRootProviders, brandMultiRootProvider, isMultiRootProvider } from "./providers.js";
|
|
6
|
+
import { t as MultiRootFileSystem } from "./fs-Nlp2AiRX.js";
|
|
7
|
+
import { a as parseRevealedView, i as parsePanelCall, n as PANEL_ENDPOINTS, o as parseRootsView, t as PANEL_CHANNEL } from "./contract-DZ4zczDT.js";
|
|
6
8
|
import { MultiRootScopeService, renderWorkspaceRootsContext, sanitizeAdditionalRoots } from "./scope.js";
|
|
7
|
-
import { c as
|
|
8
|
-
import { a as renderRootsReport, c as
|
|
9
|
+
import { a as MultiRootRegistry, c as multiRootDomainSpec, i as MAX_ALIAS_LENGTH, l as RegistryAuthorityLease, n as DOMAIN_NAME, o as REGISTRY_CONTENDED_MESSAGE, r as MAX_ADDITIONAL_ROOTS_CEILING, t as DEFAULT_MAX_ADDITIONAL_ROOTS, u as RegistryLeaseContendedError } from "./registry-DCxC5Lay.js";
|
|
10
|
+
import { a as renderRootsReport, c as revealArgv, i as parseFoldersCommand, o as resolvePanelPrimaryRoot, t as COMMAND_NAME } from "./command-CESuwlVt.js";
|
|
9
11
|
//#region src/index.ts
|
|
10
12
|
/**
|
|
11
13
|
* Carrier plugin face for the bare-package-name loader row (see the module
|
|
@@ -16,4 +18,4 @@ import { a as renderRootsReport, c as PANEL_CHANNEL, d as parseFilesView, f as p
|
|
|
16
18
|
const inject = [];
|
|
17
19
|
function apply() {}
|
|
18
20
|
//#endregion
|
|
19
|
-
export { COMMAND_NAME, CORE_PACKAGES, DEFAULT_MAX_BYTES, DOMAIN_NAME, DshCompatUnsupportedError, MAX_ALIAS_LENGTH, MultiRootCompatService, MultiRootFileSystem, MultiRootRegistry, MultiRootSandboxProvider, MultiRootScopeService, OPTIONAL_CORE_PACKAGES, PANEL_CHANNEL, PANEL_ENDPOINTS, PLUGIN_SOURCE, REGISTRY_CONTENDED_MESSAGE, REQUIRED_CORE_PACKAGES, RegistryAuthorityLease, RegistryLeaseContendedError, RootValidationError, SUPPORTED_DSH_RELEASES, additionalRootId, apply, assertSupportedInstallation, availableRoots, canonicalRoot, classifyInstallation, classifyStoredRoots, compatEnforcement, composeInstructionMessage, createInstructionMessage, expandRootInput, indexedStatuses, inject, instructionsApi, isCanonicallyUnder,
|
|
21
|
+
export { AGENT_PTY_MARKER, COMMAND_NAME, CORE_PACKAGES, DEFAULT_MAX_ADDITIONAL_ROOTS, DEFAULT_MAX_BYTES, DOMAIN_NAME, DshCompatUnsupportedError, HOST_SERVICE_PACKAGES, MAX_ADDITIONAL_ROOTS_CEILING, MAX_ALIAS_LENGTH, MULTI_ROOT_PROVIDER_BRAND, MultiRootCompatService, MultiRootFileSystem, MultiRootProviders, MultiRootRegistry, MultiRootSandboxProvider, MultiRootScopeService, OPTIONAL_CORE_PACKAGES, PANEL_CHANNEL, PANEL_ENDPOINTS, PLUGIN_SOURCE, PtyMarkerContractError, REGISTRY_CONTENDED_MESSAGE, REQUIRED_CORE_PACKAGES, RUNNER_ANCHORED_PACKAGES, RUNTIME_OPTIONAL_PACKAGES, RegistryAuthorityLease, RegistryLeaseContendedError, RootValidationError, SUPPORTED_DSH_RELEASES, additionalRootId, agentPtyMarkerVerdict, apply, assertAgentPtyMarker, assertSupportedInstallation, availableRoots, brandMultiRootProvider, canonicalRoot, classifyInstallation, classifyStoredRoots, compatEnforcement, composeInstructionMessage, createInstructionMessage, expandRootInput, indexedStatuses, inject, instructionsApi, isCanonicallyUnder, isMultiRootProvider, multiRootDomainSpec, parseFoldersCommand, parsePanelCall, parseRevealedView, parseRootsView, readInstalledVersion, removeStatusAt, renderInstructions, renderRootsReport, renderWorkspaceRootsContext, resolvePanelPrimaryRoot, resolveRootRef, revealArgv, sanitizeAdditionalRoots, validateRootCandidate, widenConfined };
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { t as effectiveDirectory } from "./session-directory-BLOstut1.js";
|
|
2
2
|
import { t as aroundTool } from "./tool-context-CpgIFcCB.js";
|
|
3
|
-
import { l as isCanonicallyUnder } from "./roots-
|
|
3
|
+
import { l as isCanonicallyUnder } from "./roots-B-0l8mNG.js";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import "@deepseek-ai/cordis";
|
|
6
|
+
import { basename, dirname, isAbsolute, relative, resolve, sep } from "node:path";
|
|
6
7
|
import { createHash } from "node:crypto";
|
|
7
8
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
8
|
-
import { basename, dirname, isAbsolute, relative, resolve, sep } from "node:path";
|
|
9
9
|
import { canonicalPath } from "@deepseek-ai/dsh-sandbox";
|
|
10
10
|
//#region src/compat/agent-instructions.ts
|
|
11
11
|
/**
|
|
@@ -430,6 +430,7 @@ function composeInstructionMessage(rendered, revoked, removed) {
|
|
|
430
430
|
/** This row is gated on the compatibility contract like every other provider. */
|
|
431
431
|
const inject = [
|
|
432
432
|
"multiRootCompat",
|
|
433
|
+
"multiRootProviders",
|
|
433
434
|
"multiRootScope",
|
|
434
435
|
"sandboxPolicy"
|
|
435
436
|
];
|
package/lib/instructions.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as composeInstructionMessage, i as apply, n as DEFAULT_MAX_SOURCE_BYTES, o as inject, r as PLUGIN_SOURCE, s as touchedPathOfToolCall, t as DEFAULT_MAX_BYTES } from "./instructions-
|
|
1
|
+
import { a as composeInstructionMessage, i as apply, n as DEFAULT_MAX_SOURCE_BYTES, o as inject, r as PLUGIN_SOURCE, s as touchedPathOfToolCall, t as DEFAULT_MAX_BYTES } from "./instructions-D19jRxN0.js";
|
|
2
2
|
export { DEFAULT_MAX_BYTES, DEFAULT_MAX_SOURCE_BYTES, PLUGIN_SOURCE, apply, composeInstructionMessage, inject, touchedPathOfToolCall };
|
package/lib/lsp.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { t as aroundTool } from "./tool-context-CpgIFcCB.js";
|
|
2
|
-
import {
|
|
2
|
+
import { t as routeWorkspacePath } from "./workspace-routing-YPxaQ4V3.js";
|
|
3
|
+
import { t as wrapMethod } from "./method-wrapper-CFY3p3j2.js";
|
|
3
4
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
5
|
//#region src/lsp.ts
|
|
5
|
-
const inject = [
|
|
6
|
+
const inject = [
|
|
7
|
+
"multiRootCompat",
|
|
8
|
+
"multiRootProviders",
|
|
9
|
+
"multiRootScope"
|
|
10
|
+
];
|
|
6
11
|
function apply(ctx) {
|
|
7
12
|
const callers = new AsyncLocalStorage();
|
|
8
13
|
aroundTool(ctx, (exec, next) => exec.agent === void 0 ? next() : callers.run(exec.agent.session, next));
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/method-wrapper.ts
|
|
2
|
+
/** Wrap a public method on a Cordis service without capturing a caller-context proxy. */
|
|
3
|
+
function wrapMethod(target, key, wrap) {
|
|
4
|
+
const own = Object.getOwnPropertyDescriptor(target, key);
|
|
5
|
+
let prototype = target;
|
|
6
|
+
let descriptor;
|
|
7
|
+
while (prototype !== null && descriptor === void 0) {
|
|
8
|
+
descriptor = Object.getOwnPropertyDescriptor(prototype, key);
|
|
9
|
+
prototype = Object.getPrototypeOf(prototype);
|
|
10
|
+
}
|
|
11
|
+
if (typeof descriptor?.value !== "function") throw new Error(`multi-root: ${String(key)} is not a public method`);
|
|
12
|
+
const value = wrap(descriptor.value);
|
|
13
|
+
Object.defineProperty(target, key, {
|
|
14
|
+
configurable: true,
|
|
15
|
+
writable: true,
|
|
16
|
+
enumerable: own?.enumerable ?? false,
|
|
17
|
+
value
|
|
18
|
+
});
|
|
19
|
+
return () => {
|
|
20
|
+
if (Object.getOwnPropertyDescriptor(target, key)?.value !== value) return;
|
|
21
|
+
if (own === void 0) Reflect.deleteProperty(target, key);
|
|
22
|
+
else Object.defineProperty(target, key, own);
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
export { wrapMethod as t };
|