@dsh-electron/dsh-plugin-multi-root-workspace 0.1.14 → 0.1.15
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 +20 -1
- package/CHANGELOG.md +20 -1
- package/README.en.md +24 -7
- package/README.md +24 -7
- package/cordis.patch.yml +5 -1
- package/lib/{llm-message-fYORacvh.js → agent-instructions-BXuKhOzr.js} +1 -84
- package/lib/auto-review.js +2 -2
- package/lib/client.js +208 -26
- package/lib/client.js.map +1 -1
- package/lib/{command-Dcz6nULo.js → command-y5tfcAeC.js} +21 -10
- package/lib/command.js +1 -1
- package/lib/compat-CTJbpCxu.js +267 -0
- package/lib/compat.js +1 -1
- package/lib/{dialects-Dr4rDR-H.js → dialects-CdlG3RF9.js} +1 -1
- package/lib/{compat-veDqCOoa.js → dsh-version-DCwF7REk.js} +2 -264
- package/lib/index.js +10 -8
- package/lib/{instructions-KsUwUFE0.js → instructions-Ck9wp4J4.js} +695 -26
- package/lib/instructions.js +1 -1
- package/lib/llm-message-CepyzEBQ.js +84 -0
- package/lib/lsp.js +3 -3
- package/lib/{metadata-DgqSdVX_.js → metadata-99jgViHQ.js} +1 -1
- package/lib/{method-wrapper-CFY3p3j2.js → method-wrapper-D4oUCsEF.js} +22 -2
- package/lib/{native-win32-CRNXr11U.js → native-win32-C5oMtt7f.js} +1 -1
- package/lib/ops-compatibility.js +58 -0
- package/lib/ops-settings-UN2oOKEp.js +40 -0
- package/lib/ops-tools-k4JYaOrM.js +152 -0
- package/lib/process-fence.js +100 -24
- package/lib/{protocol-CnR1KTXy.js → protocol-CwDnFEyK.js} +8 -4
- package/lib/{registry-DR_sMuvS.js → registry-DyDNDSJZ.js} +235 -79
- package/lib/{registry-lease-win32-DDO-c7ig.js → registry-lease-win32-r4puac9m.js} +1 -1
- package/lib/registry.js +2 -2
- package/lib/{review-context-NDpKeMCc.js → review-context-B-oHkU6a.js} +24 -7
- package/lib/review-context.js +1 -1
- package/lib/{roots-Ca_W1S9n.js → roots-CmaWxRVy.js} +112 -22
- package/lib/{sandbox-C-vCJCp3.js → sandbox-CZElTNPz.js} +1 -1
- package/lib/sandbox.js +1 -1
- package/lib/scope.js +72 -9
- package/lib/skills-utyuvmwr.js +104 -0
- package/lib/skills.js +4 -105
- package/lib/{tool-context-CpgIFcCB.js → tool-context-CCeY3mrK.js} +1 -1
- package/lib/types/authority/protocol.d.ts +5 -1
- package/lib/types/client/locales.d.ts +28 -0
- package/lib/types/client/ops-settings.d.ts +6 -0
- package/lib/types/client/skills-settings.d.ts +3 -2
- package/lib/types/command.d.ts +3 -3
- package/lib/types/compat/instruction-activity.d.ts +32 -1
- package/lib/types/compat/ops-confirmation.d.ts +17 -0
- package/lib/types/compat/ops-settings.d.ts +11 -0
- package/lib/types/compat/ops-tools.d.ts +25 -0
- package/lib/types/compat/skills-client.d.ts +1 -0
- package/lib/types/compat/tool-context.d.ts +4 -0
- package/lib/types/compat.d.ts +2 -2
- package/lib/types/instructions.d.ts +6 -1
- package/lib/types/ops-compatibility.d.ts +34 -0
- package/lib/types/registry.d.ts +67 -8
- package/lib/types/review-context.d.ts +2 -0
- package/lib/types/roots.d.ts +114 -20
- package/lib/types/scope.d.ts +28 -2
- package/lib/workspace-files.js +3 -3
- package/lib/{workspace-routing-YPxaQ4V3.js → workspace-routing-D_qwMUh-.js} +4 -4
- package/package.json +5 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { t as effectiveDirectory } from "./session-directory-BLOstut1.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { c as stableOpsJson, l as touchedPathOfToolCall, s as pendingInstructionTargets } from "./instructions-Ck9wp4J4.js";
|
|
3
|
+
import { r as opsCompatibilityToken } from "./ops-settings-UN2oOKEp.js";
|
|
4
|
+
import { l as isCanonicallyUnder } from "./roots-CmaWxRVy.js";
|
|
4
5
|
import { createHash } from "node:crypto";
|
|
5
6
|
import { resolve } from "node:path";
|
|
6
7
|
import { Service } from "@deepseek-ai/cordis";
|
|
@@ -50,7 +51,7 @@ var MultiRootReviewContext = class extends Service {
|
|
|
50
51
|
const policy = this.ctx.sandboxPolicy.resolve({ session: agent.session });
|
|
51
52
|
const primary = canonicalPath(policy.workspaceRoot);
|
|
52
53
|
if (agent.session.header.cwd !== void 0 && canonicalPath(agent.session.header.cwd) !== primary) throw new Error("review-context: inconsistent session origin");
|
|
53
|
-
await this.ctx.multiRootRegistry.refresh(
|
|
54
|
+
await this.ctx.multiRootRegistry.refresh(policy.workspaceRoot);
|
|
54
55
|
signal.throwIfAborted();
|
|
55
56
|
const cwd = effectiveDirectory(this.ctx, agent.session);
|
|
56
57
|
if (cwd === void 0) throw new Error("review-context: missing effective cwd");
|
|
@@ -73,10 +74,20 @@ var MultiRootReviewContext = class extends Service {
|
|
|
73
74
|
primaryRootWritable: policy.mode !== "read-only" && !scope.deniedRoots.some((deny) => isCanonicallyUnder(scope.primaryRoot, deny)),
|
|
74
75
|
writeBoundary: scope.authority === "unavailable" && policy.mode !== "read-only" ? "primary-root-only; no shared temp grants; explicit denies take precedence" : policy.mode === "danger-full-access" ? "ambient host writes minus explicit deny boundaries; membership is not an access allowlist" : policy.mode === "workspace-write" ? "primary root, effective writable additional roots and upstream host temp grants; explicit denies take precedence" : "only effective writable additional roots; explicit denies take precedence"
|
|
75
76
|
};
|
|
76
|
-
const
|
|
77
|
-
const
|
|
78
|
-
const
|
|
77
|
+
const official = touchedPathOfToolCall(exec.name, JSON.stringify(exec.arguments));
|
|
78
|
+
const modeToken = opsCompatibilityToken(this.ctx);
|
|
79
|
+
const candidates = modeToken === void 0 ? [] : pendingInstructionTargets(exec.name, exec.arguments);
|
|
80
|
+
const targets = [];
|
|
81
|
+
for (const path of official === void 0 ? candidates : [official]) targets.push(this.ctx.fs.processPath(await this.ctx.fs.resolve(resolve(cwd, path), { signal })));
|
|
82
|
+
const pending = exec.callId === void 0 || modeToken === void 0 ? void 0 : {
|
|
83
|
+
callId: exec.callId,
|
|
84
|
+
name: exec.name,
|
|
85
|
+
arguments: exec.arguments,
|
|
86
|
+
parent: exec.parent
|
|
87
|
+
};
|
|
88
|
+
const constraints = await this.ctx.multiRootInstructions.snapshot(agent, scope.authority === "available" ? scope.additionalRoots : [], targets, signal, pending);
|
|
79
89
|
signal.throwIfAborted();
|
|
90
|
+
if (opsCompatibilityToken(this.ctx) !== modeToken) throw new Error("review-context: OPS compatibility changed during the build; retry");
|
|
80
91
|
const payload = {
|
|
81
92
|
schemaVersion: 1,
|
|
82
93
|
environment,
|
|
@@ -87,7 +98,13 @@ var MultiRootReviewContext = class extends Service {
|
|
|
87
98
|
if (Buffer.byteLength(JSON.stringify(payload), "utf8") > 65536) throw new Error("review-context: context budget exceeded");
|
|
88
99
|
return {
|
|
89
100
|
...payload,
|
|
90
|
-
fingerprint: createHash("sha256").update(JSON.stringify([payload, nativeInstructionFingerprint(agent.session)]
|
|
101
|
+
fingerprint: createHash("sha256").update(JSON.stringify(modeToken === void 0 ? [payload, nativeInstructionFingerprint(agent.session)] : [
|
|
102
|
+
payload,
|
|
103
|
+
nativeInstructionFingerprint(agent.session),
|
|
104
|
+
exec.name,
|
|
105
|
+
stableOpsJson(exec.arguments),
|
|
106
|
+
modeToken.id
|
|
107
|
+
])).digest("hex")
|
|
91
108
|
};
|
|
92
109
|
}
|
|
93
110
|
async assertCurrent(exec, snapshot, signal) {
|
package/lib/review-context.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as MultiRootReviewContext } from "./review-context-
|
|
1
|
+
import { t as MultiRootReviewContext } from "./review-context-B-oHkU6a.js";
|
|
2
2
|
export { MultiRootReviewContext, MultiRootReviewContext as default };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { i as isWindowsNetworkOrDevicePath } from "./windows-runner-contract-B5R2K3mM.js";
|
|
2
2
|
import { statSync } from "node:fs";
|
|
3
|
-
import { isAbsolute, join, sep } from "node:path";
|
|
3
|
+
import { isAbsolute, join, resolve, sep } from "node:path";
|
|
4
4
|
import { canonicalPath } from "@deepseek-ai/dsh-sandbox";
|
|
5
5
|
import { homedir } from "node:os";
|
|
6
6
|
//#region src/roots.ts
|
|
@@ -20,15 +20,21 @@ import { homedir } from "node:os";
|
|
|
20
20
|
* misconfiguration, not a path to guess at.
|
|
21
21
|
* 2. `missing` — the directory does not exist.
|
|
22
22
|
* 3. `not-a-directory` — it exists but is not a directory.
|
|
23
|
-
* 4. `
|
|
23
|
+
* 4. `protected-path` — the canonical candidate equals, lies inside, or
|
|
24
|
+
* contains a protected DSH directory (the DSH home, the registry's
|
|
25
|
+
* physical storage medium, or the lease directory, supplied through
|
|
26
|
+
* {@link RootCandidateContext.protectedPaths}): those hold the store,
|
|
27
|
+
* the lease, profiles and user credentials, so no access level may
|
|
28
|
+
* register them. Applies to every mode; no remote exemption exists.
|
|
29
|
+
* 5. `equals-primary` — the canonical candidate IS the session's workspace
|
|
24
30
|
* root, which is already granted by the upstream policy.
|
|
25
|
-
*
|
|
31
|
+
* 6. `primary-overlap` — the candidate lies under the workspace root, or
|
|
26
32
|
* contains it. The union grants nothing new, and the displayed list would
|
|
27
33
|
* stop matching the enforced root set.
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* Same reasoning as rule
|
|
31
|
-
*
|
|
34
|
+
* 7. `duplicate` — another registered root canonicalizes to the same path.
|
|
35
|
+
* 8. `nested` — the candidate lies under a registered root, or contains one.
|
|
36
|
+
* Same reasoning as rule 6, applied to the additional roots.
|
|
37
|
+
* 9. `unsupported-path` — on Windows, a network or device path (`\\server`,
|
|
32
38
|
* `\\?\…`, `\\.\…`): the ACL confinement cannot be shown to cover it, so it
|
|
33
39
|
* is rejected lexically before any filesystem touch.
|
|
34
40
|
*
|
|
@@ -88,6 +94,22 @@ const CASE_SENSITIVE = process.platform !== "win32";
|
|
|
88
94
|
function comparable(path) {
|
|
89
95
|
return CASE_SENSITIVE ? path : path.toLowerCase();
|
|
90
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* The Registry/scope lookup key of one primary root: the UPSTREAM spelling,
|
|
99
|
+
* normalized lexically by `path.resolve` and case-folded on Windows — never
|
|
100
|
+
* re-resolved. Upstream `dsh-sandbox-policy` sets `policy.workspaceRoot` to
|
|
101
|
+
* `session.header.cwd` verbatim, so the spelling IS the identity: a symlinked
|
|
102
|
+
* workspace and its realpath target are different sessions and different key
|
|
103
|
+
* spaces. Re-resolving here would let a replaced workspace directory
|
|
104
|
+
* silently re-key the record set onto another identity — the exact failure
|
|
105
|
+
* the redirect rules exist to prevent. This is the ONLY function allowed to
|
|
106
|
+
* turn a primary root into a Registry key; path geometry keeps using
|
|
107
|
+
* {@link canonicalRoot}.
|
|
108
|
+
*/
|
|
109
|
+
function primaryKey(path, platform = process.platform) {
|
|
110
|
+
const resolved = resolve(path);
|
|
111
|
+
return platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
112
|
+
}
|
|
91
113
|
/** Whether two canonical paths name the same directory on this platform. */
|
|
92
114
|
function samePath(left, right) {
|
|
93
115
|
return comparable(left) === comparable(right);
|
|
@@ -131,6 +153,11 @@ function validateRootCandidate(raw, context) {
|
|
|
131
153
|
const canonical = canonicalRoot(expanded);
|
|
132
154
|
if (platform === "win32" && isWindowsNetworkOrDevicePath(canonical)) throw unsupportedPath(expanded);
|
|
133
155
|
assertDirectory(canonical, expanded);
|
|
156
|
+
const protectedConflict = protectedOverlap(canonical, context.protectedPaths ?? []);
|
|
157
|
+
if (protectedConflict !== void 0) throw new RootValidationError("protected-path", `"${canonical}" overlaps the protected DSH directory "${protectedConflict.path}" (${protectedConflict.kind}) and cannot be registered`, {
|
|
158
|
+
conflict: protectedConflict.path,
|
|
159
|
+
reference: canonical
|
|
160
|
+
});
|
|
134
161
|
const primaryRoot = canonicalRoot(context.primaryRoot);
|
|
135
162
|
if (samePath(canonical, primaryRoot)) throw new RootValidationError("equals-primary", `"${canonical}" is this session's workspace root and is already writable`, {
|
|
136
163
|
conflict: primaryRoot,
|
|
@@ -160,6 +187,18 @@ function validateRootCandidate(raw, context) {
|
|
|
160
187
|
function unsupportedPath(asTyped) {
|
|
161
188
|
return new RootValidationError("unsupported-path", `"${asTyped}" is a network or device path, which Windows ACL confinement cannot cover`, { reference: asTyped });
|
|
162
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* The first protected directory `candidate` overlaps — equals, lies inside,
|
|
192
|
+
* or contains. Both sides must already be canonical (or absolute boundary
|
|
193
|
+
* spellings): the comparison is the same lexical three-way test the other
|
|
194
|
+
* boundary rules use, so a symlink alias can never route around it.
|
|
195
|
+
* @param candidate - the canonical candidate or recorded grant to test.
|
|
196
|
+
* @param protectedPaths - the protected DSH directories.
|
|
197
|
+
* @returns the overlapping protected path, or `undefined`.
|
|
198
|
+
*/
|
|
199
|
+
function protectedOverlap(candidate, protectedPaths) {
|
|
200
|
+
return protectedPaths.find((boundary) => samePath(candidate, boundary.path) || isCanonicallyUnder(candidate, boundary.path) || isCanonicallyUnder(boundary.path, candidate));
|
|
201
|
+
}
|
|
163
202
|
/** Assert that a canonical path is an existing directory. */
|
|
164
203
|
function assertDirectory(canonical, asTyped) {
|
|
165
204
|
let stats;
|
|
@@ -181,12 +220,15 @@ function assertDirectory(canonical, asTyped) {
|
|
|
181
220
|
* @param primaryRoot - the session workspace root the records belong to.
|
|
182
221
|
* @param records - the stored records, in registry order.
|
|
183
222
|
* @param platform - the platform the rules run for; defaults to `process.platform`.
|
|
223
|
+
* @param protectedPaths - the protected DSH directories a stored grant may not
|
|
224
|
+
* overlap; a record that does is reported `invalid` with
|
|
225
|
+
* `invalidReason: 'protected-path'` — withheld, never deleted, never re-granted.
|
|
184
226
|
* @returns one status per record, in the same order.
|
|
185
227
|
*/
|
|
186
|
-
function classifyStoredRoots(primaryRoot, records, platform = process.platform) {
|
|
228
|
+
function classifyStoredRoots(primaryRoot, records, platform = process.platform, protectedPaths = []) {
|
|
187
229
|
const primary = canonicalRoot(primaryRoot);
|
|
188
230
|
const duplicated = duplicatedIds(records);
|
|
189
|
-
return markMembershipConflicts(records.map((record, index) => classifyStoredRoot(record, primary, duplicated.has(record.id), index, platform)));
|
|
231
|
+
return markMembershipConflicts(records.map((record, index) => classifyStoredRoot(record, primary, duplicated.has(record.id), index, platform, protectedPaths)));
|
|
190
232
|
}
|
|
191
233
|
/**
|
|
192
234
|
* Pass two of {@link classifyStoredRoots}: every pair of structurally sound
|
|
@@ -204,7 +246,7 @@ function classifyStoredRoots(primaryRoot, records, platform = process.platform)
|
|
|
204
246
|
* when the pair found first was already doomed.
|
|
205
247
|
*/
|
|
206
248
|
function markMembershipConflicts(statuses) {
|
|
207
|
-
const boundaries = statuses.map((status) => status.state
|
|
249
|
+
const boundaries = statuses.map((status) => status.state !== "invalid" || status.invalidReason === "protected-path" || status.invalidReason === "primary-overlap" ? singleGrantOf(status) : void 0);
|
|
208
250
|
const conflicts = /* @__PURE__ */ new Map();
|
|
209
251
|
for (let index = 0; index < boundaries.length; index += 1) {
|
|
210
252
|
const boundary = boundaries[index];
|
|
@@ -221,7 +263,7 @@ function markMembershipConflicts(statuses) {
|
|
|
221
263
|
if (conflicts.size === 0) return [...statuses];
|
|
222
264
|
return statuses.map((status, index) => {
|
|
223
265
|
const others = conflicts.get(index);
|
|
224
|
-
if (others === void 0) return status;
|
|
266
|
+
if (others === void 0 || status.invalidReason === "protected-path" || status.invalidReason === "primary-overlap") return status;
|
|
225
267
|
const boundary = boundaries[index];
|
|
226
268
|
const partners = others.map((other) => `"${other}"`).join(", ");
|
|
227
269
|
return {
|
|
@@ -271,7 +313,7 @@ function duplicatedIds(records) {
|
|
|
271
313
|
* boundary — the directory the operator was granted — not what `path`
|
|
272
314
|
* resolves to today.
|
|
273
315
|
*/
|
|
274
|
-
function classifyStoredRoot(record, primary, duplicatedId, position, platform) {
|
|
316
|
+
function classifyStoredRoot(record, primary, duplicatedId, position, platform, protectedPaths) {
|
|
275
317
|
const invalid = (detail) => ({
|
|
276
318
|
...record,
|
|
277
319
|
state: "invalid",
|
|
@@ -291,8 +333,21 @@ function classifyStoredRoot(record, primary, duplicatedId, position, platform) {
|
|
|
291
333
|
if (typeof recorded !== "string" || recorded === "") return invalid("the record does not say which directory it was granted for");
|
|
292
334
|
if (!isAbsolute(recorded)) return invalid(`the recorded grant "${recorded}" is not an absolute path`);
|
|
293
335
|
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
|
-
|
|
295
|
-
|
|
336
|
+
const primaryOverlap = (detail) => ({
|
|
337
|
+
...record,
|
|
338
|
+
state: "invalid",
|
|
339
|
+
invalidReason: "primary-overlap",
|
|
340
|
+
detail: `${detail}; any recorded read-only restriction remains in force`
|
|
341
|
+
});
|
|
342
|
+
if (samePath(recorded, primary)) return primaryOverlap(`"${recorded}" is this session's workspace root`);
|
|
343
|
+
if (isCanonicallyUnder(recorded, primary) || isCanonicallyUnder(primary, recorded)) return primaryOverlap(`"${recorded}" overlaps this session's workspace root`);
|
|
344
|
+
const protectedConflict = protectedOverlap(recorded, protectedPaths);
|
|
345
|
+
if (protectedConflict !== void 0) return {
|
|
346
|
+
...record,
|
|
347
|
+
state: "invalid",
|
|
348
|
+
invalidReason: "protected-path",
|
|
349
|
+
detail: protectedGrantDetail(recorded, protectedConflict)
|
|
350
|
+
};
|
|
296
351
|
const canonical = canonicalRoot(record.path);
|
|
297
352
|
if (!samePath(canonical, recorded)) return {
|
|
298
353
|
...record,
|
|
@@ -325,23 +380,58 @@ function classifyStoredRoot(record, primary, duplicatedId, position, platform) {
|
|
|
325
380
|
function availableRoots(statuses) {
|
|
326
381
|
return statuses.filter((status) => status.state === "available").map((status) => status.path);
|
|
327
382
|
}
|
|
383
|
+
/** The detail text the protected-path verdict carries, shared by both marking paths. */
|
|
384
|
+
function protectedGrantDetail(grant, conflict) {
|
|
385
|
+
return `the recorded grant "${grant}" overlaps the protected DSH directory "${conflict.path}" (${conflict.kind}); it stays registered and withheld, and any recorded read-only restriction remains in force`;
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Apply the protected-path verdict to an already-classified list: a status
|
|
389
|
+
* whose recorded grant overlaps a protected directory becomes `invalid` with
|
|
390
|
+
* `invalidReason: 'protected-path'` — the same verdict the pass-one stored
|
|
391
|
+
* check produces, here applied by a consumer that classified elsewhere (a
|
|
392
|
+
* shared-registry follower re-judging an owner snapshot against ITS OWN
|
|
393
|
+
* protected set, which may differ from the owner's). Statuses already
|
|
394
|
+
* `invalid` keep their own verdict — a structural failure is never healed
|
|
395
|
+
* into a trustworthy boundary.
|
|
396
|
+
* @param statuses - the classified statuses to re-mark.
|
|
397
|
+
* @param protectedPaths - the caller's own protected DSH directories.
|
|
398
|
+
* @returns a new list with the verdict applied.
|
|
399
|
+
*/
|
|
400
|
+
function markProtectedPaths(statuses, protectedPaths) {
|
|
401
|
+
if (protectedPaths.length === 0) return [...statuses];
|
|
402
|
+
return statuses.map((status) => {
|
|
403
|
+
if (status.state === "invalid") return status;
|
|
404
|
+
const grant = singleGrantOf(status);
|
|
405
|
+
if (grant === void 0) return status;
|
|
406
|
+
const conflict = protectedOverlap(grant, protectedPaths);
|
|
407
|
+
if (conflict === void 0) return status;
|
|
408
|
+
return {
|
|
409
|
+
...status,
|
|
410
|
+
state: "invalid",
|
|
411
|
+
invalidReason: "protected-path",
|
|
412
|
+
detail: protectedGrantDetail(grant, conflict)
|
|
413
|
+
};
|
|
414
|
+
});
|
|
415
|
+
}
|
|
328
416
|
/**
|
|
329
417
|
* The recorded boundary one status still RESERVES: the grant it was
|
|
330
418
|
* registered for, which a new or revived registration may not overlap merely
|
|
331
419
|
* because the root is currently unusable.
|
|
332
420
|
*
|
|
333
|
-
* `available`, `missing`, `redirected`, and
|
|
334
|
-
*
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
-
*
|
|
338
|
-
*
|
|
421
|
+
* `available`, `missing`, `redirected`, and the trustworthy-boundary
|
|
422
|
+
* `invalid` verdicts (`membership-conflict`, `protected-path`, and
|
|
423
|
+
* `primary-overlap`) all carry
|
|
424
|
+
* an intact recorded grant; every other `invalid` verdict means the grant
|
|
425
|
+
* could not be parsed at all, so it reserves — and denies — nothing. This is
|
|
426
|
+
* not an independent authorization table: it derives the boundary from the
|
|
427
|
+
* status list {@link classifyStoredRoots} produced, with the same
|
|
428
|
+
* grant-extraction semantics, so its answer can never disagree with
|
|
339
429
|
* classification.
|
|
340
430
|
* @param status - one classified registration.
|
|
341
431
|
* @returns the absolute recorded boundary, or `undefined` when there is none.
|
|
342
432
|
*/
|
|
343
433
|
function reservedRootBoundary(status) {
|
|
344
|
-
if (status.state === "invalid" && status.invalidReason
|
|
434
|
+
if (status.state === "invalid" && status.invalidReason === void 0) return void 0;
|
|
345
435
|
const boundary = singleGrantOf(status);
|
|
346
436
|
return boundary !== void 0 && isAbsolute(boundary) ? boundary : void 0;
|
|
347
437
|
}
|
|
@@ -439,4 +529,4 @@ function describeRef(ref) {
|
|
|
439
529
|
}
|
|
440
530
|
}
|
|
441
531
|
//#endregion
|
|
442
|
-
export { classifyStoredRoots as a, indexedStatuses as c,
|
|
532
|
+
export { samePath as _, classifyStoredRoots as a, indexedStatuses as c, primaryKey as d, protectedOverlap as f, resolveRootRef as g, resolveRootIndex as h, canonicalRoot as i, isCanonicallyUnder as l, reservedRootBoundary as m, additionalRootId as n, entryRootRef as o, removeStatusAt as p, availableRoots as r, expandRootInput as s, RootValidationError as t, markProtectedPaths as u, singleGrantOf as v, validateRootCandidate as y };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as parseWindowsProfile, i as isWindowsNetworkOrDevicePath, n as assertWindowsDenyCapacity, t as DialectUnrecognizedError } from "./windows-runner-contract-B5R2K3mM.js";
|
|
2
|
-
import { a as splitConfined, c as mapConfined, i as restrictProfileArgs, l as widenConfined, o as widenProfileArgs, r as graftProfileArgs, t as denyProfileArgs } from "./dialects-
|
|
2
|
+
import { a as splitConfined, c as mapConfined, i as restrictProfileArgs, l as widenConfined, o as widenProfileArgs, r as graftProfileArgs, t as denyProfileArgs } from "./dialects-CdlG3RF9.js";
|
|
3
3
|
import { brandMultiRootProvider } from "./providers.js";
|
|
4
4
|
import { SandboxUnavailableError, canonicalPath } from "@deepseek-ai/dsh-sandbox";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
package/lib/sandbox.js
CHANGED
package/lib/scope.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import { t as wrapMethod } from "./method-wrapper-D4oUCsEF.js";
|
|
1
2
|
import { i as rememberOperationProfile, r as operationPermitsProfile, t as currentRegistryOperation } from "./registry-operation-C2ZV3Jhw.js";
|
|
2
|
-
import { t as aroundTool } from "./tool-context-
|
|
3
|
+
import { t as aroundTool } from "./tool-context-CCeY3mrK.js";
|
|
4
|
+
import { r as opsCompatibilityToken } from "./ops-settings-UN2oOKEp.js";
|
|
5
|
+
import { d as primaryKey } from "./roots-CmaWxRVy.js";
|
|
6
|
+
import { a as createOpsProtectionState, l as opsDispatchBarrier, u as opsSpawnPermitted } from "./ops-tools-k4JYaOrM.js";
|
|
3
7
|
import { r as effectiveRootAccess } from "./contract-CStSEOcK.js";
|
|
4
8
|
import { statSync } from "node:fs";
|
|
5
9
|
import { isAbsolute } from "node:path";
|
|
@@ -156,6 +160,7 @@ var MultiRootScopeService = class extends Service {
|
|
|
156
160
|
listeners = /* @__PURE__ */ new Set();
|
|
157
161
|
/** Enclosing {@link MultiRootScopeService.batch} depth; inner calls nest. */
|
|
158
162
|
batchDepth = 0;
|
|
163
|
+
opsProtection = createOpsProtectionState();
|
|
159
164
|
/** True when a batched write coalesced its notification into the flush. */
|
|
160
165
|
batchDirty = false;
|
|
161
166
|
admissionProof;
|
|
@@ -179,6 +184,13 @@ var MultiRootScopeService = class extends Service {
|
|
|
179
184
|
* treated as evidence.
|
|
180
185
|
*/
|
|
181
186
|
dshHome;
|
|
187
|
+
/**
|
|
188
|
+
* Whether the row carried a `dshHome` value at all — even an invalid one.
|
|
189
|
+
* The registry's protected-boundary set must tell "no home configured"
|
|
190
|
+
* (absent: nothing to protect) from "a home was supplied and rejected"
|
|
191
|
+
* (invalid: the boundary cannot be proven).
|
|
192
|
+
*/
|
|
193
|
+
dshHomeSupplied;
|
|
182
194
|
/** Observe registry publications; consumers still resolve authority from scopeOf(). */
|
|
183
195
|
subscribe(listener) {
|
|
184
196
|
this.listeners.add(listener);
|
|
@@ -228,6 +240,20 @@ var MultiRootScopeService = class extends Service {
|
|
|
228
240
|
get toolAccessInvalid() {
|
|
229
241
|
return this.capsInvalid;
|
|
230
242
|
}
|
|
243
|
+
/**
|
|
244
|
+
* The validated `dshHome` row config, classified for the registry's
|
|
245
|
+
* protected-boundary set: `absent` when the row carried no value (nothing
|
|
246
|
+
* to protect), `invalid` when it carried a value the constructor rejected
|
|
247
|
+
* (the boundary cannot be proven), `configured` with the accepted absolute
|
|
248
|
+
* path. Read-only — this changes no scope or `protectedHome` semantics.
|
|
249
|
+
*/
|
|
250
|
+
get dshHomeConfig() {
|
|
251
|
+
if (this.dshHome !== void 0) return {
|
|
252
|
+
kind: "configured",
|
|
253
|
+
path: this.dshHome
|
|
254
|
+
};
|
|
255
|
+
return this.dshHomeSupplied ? { kind: "invalid" } : { kind: "absent" };
|
|
256
|
+
}
|
|
231
257
|
constructor(ctx, config = {}) {
|
|
232
258
|
super(ctx, "multiRootScope");
|
|
233
259
|
const toolAccess = config.toolAccess;
|
|
@@ -243,10 +269,44 @@ var MultiRootScopeService = class extends Service {
|
|
|
243
269
|
this.capsInvalid = false;
|
|
244
270
|
}
|
|
245
271
|
const dshHome = config.dshHome;
|
|
272
|
+
this.dshHomeSupplied = dshHome !== void 0;
|
|
246
273
|
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`);
|
|
247
274
|
else this.dshHome = dshHome;
|
|
248
275
|
aroundTool(ctx, (exec, next) => {
|
|
249
|
-
return
|
|
276
|
+
return this.withToolCap(exec.name, async () => {
|
|
277
|
+
const denial = opsDispatchBarrier(ctx, this, exec);
|
|
278
|
+
if (denial !== void 0) throw new Error(denial);
|
|
279
|
+
return await next();
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
ctx.inject([
|
|
283
|
+
"tools",
|
|
284
|
+
"sandboxPolicy",
|
|
285
|
+
"multiRootCompat",
|
|
286
|
+
"multiRootProviders"
|
|
287
|
+
], (scoped) => {
|
|
288
|
+
if (typeof scoped.tools.guard !== "function") return;
|
|
289
|
+
scoped.effect(() => scoped.tools.guard((exec) => opsDispatchBarrier(ctx, this, exec)));
|
|
290
|
+
});
|
|
291
|
+
ctx.inject([
|
|
292
|
+
"subprocess",
|
|
293
|
+
"multiRootCompat",
|
|
294
|
+
"multiRootProviders"
|
|
295
|
+
], (scoped) => {
|
|
296
|
+
scoped.effect(() => {
|
|
297
|
+
let active = true;
|
|
298
|
+
const restore = wrapMethod(scoped.get("subprocess"), "spawn", (original) => {
|
|
299
|
+
const scope = this;
|
|
300
|
+
return function(spec) {
|
|
301
|
+
if (active && opsCompatibilityToken(scoped) !== void 0 && scope.hasActiveDenyRoots() && !opsSpawnPermitted(scope.opsProtection) && !(scope.admissionRequired && scope.admissionPathsValid() && scope.permitsProfile(spec.argv))) throw new Error("multi-root workspace: an unattributed subprocess has no protected execution or live profile admission; refusing launch while OPS compatibility write boundaries apply");
|
|
302
|
+
return original.call(this, spec);
|
|
303
|
+
};
|
|
304
|
+
});
|
|
305
|
+
return () => {
|
|
306
|
+
active = false;
|
|
307
|
+
restore();
|
|
308
|
+
};
|
|
309
|
+
});
|
|
250
310
|
});
|
|
251
311
|
ctx.inject(["systemPrompt", "sandboxPolicy"], (scope) => {
|
|
252
312
|
scope.systemPrompt.context({
|
|
@@ -327,16 +387,19 @@ var MultiRootScopeService = class extends Service {
|
|
|
327
387
|
previewForTool(policy, toolName) {
|
|
328
388
|
return this.project(policy, this.capForTool(toolName));
|
|
329
389
|
}
|
|
390
|
+
withToolCap(name, fn) {
|
|
391
|
+
return toolCapStore.run({ capped: this.capForTool(name) }, fn);
|
|
392
|
+
}
|
|
330
393
|
/** Execution and review use exactly the same parent-narrowing rule. */
|
|
331
394
|
capForTool(toolName) {
|
|
332
395
|
return this.capsInvalid || toolCapStore.getStore()?.capped === true || this.caps[toolName] === "read-only";
|
|
333
396
|
}
|
|
334
397
|
project(policy, capped, recorded, authority = this.registryAuthority) {
|
|
335
398
|
const primaryRoot = canonicalPath(policy.workspaceRoot);
|
|
336
|
-
const registered = recorded ?? this.rootsByPrimary.get(
|
|
399
|
+
const registered = recorded ?? this.rootsByPrimary.get(primaryKey(policy.workspaceRoot)) ?? [];
|
|
337
400
|
const roots = authority === "unavailable" ? [] : sanitizeMembers(primaryRoot, registered);
|
|
338
401
|
const writable = capped || authority === "unavailable" ? [] : roots.filter((root) => effectivelyWritable(policy.mode, root.access)).map((root) => root.path);
|
|
339
|
-
const denied = registered.filter((root) => typeof root.recordedPath === "string" && root.recordedPath !== "" &&
|
|
402
|
+
const denied = registered.filter((root) => typeof root.recordedPath === "string" && root.recordedPath !== "" && (capped || !effectivelyWritable(policy.mode, root.access))).map((root) => root.recordedPath);
|
|
340
403
|
const deniedRoots = [...new Set(denied)];
|
|
341
404
|
let protectedHome;
|
|
342
405
|
if (this.dshHome !== void 0 && policy.mode === "danger-full-access" && (deniedRoots.length > 0 || authority === "unavailable")) {
|
|
@@ -419,10 +482,9 @@ var MultiRootScopeService = class extends Service {
|
|
|
419
482
|
}
|
|
420
483
|
/** The sanitized members of one primary root, in registry order. */
|
|
421
484
|
membersOf(primaryRoot) {
|
|
422
|
-
const
|
|
423
|
-
const registered = this.rootsByPrimary.get(key);
|
|
485
|
+
const registered = this.rootsByPrimary.get(primaryKey(primaryRoot));
|
|
424
486
|
if (registered === void 0) return [];
|
|
425
|
-
return sanitizeMembers(
|
|
487
|
+
return sanitizeMembers(canonicalPath(primaryRoot), registered);
|
|
426
488
|
}
|
|
427
489
|
/**
|
|
428
490
|
* Replace the roots registered for one primary root.
|
|
@@ -430,11 +492,12 @@ var MultiRootScopeService = class extends Service {
|
|
|
430
492
|
* M1 uses this from tests and smoke scripts only; M3 feeds it from plugin
|
|
431
493
|
* storage. It is a plain setter so the transition swaps the data source
|
|
432
494
|
* without touching any provider.
|
|
433
|
-
* @param primaryRoot -
|
|
495
|
+
* @param primaryRoot - primary root the roots belong to; the Registry key
|
|
496
|
+
* is its upstream spelling via {@link primaryKey}, never re-resolved.
|
|
434
497
|
* @param roots - the additional roots, in display order.
|
|
435
498
|
*/
|
|
436
499
|
setAdditionalRoots(primaryRoot, roots) {
|
|
437
|
-
const key =
|
|
500
|
+
const key = primaryKey(primaryRoot);
|
|
438
501
|
if (roots.length === 0) this.rootsByPrimary.delete(key);
|
|
439
502
|
else this.rootsByPrimary.set(key, [...roots]);
|
|
440
503
|
this.notifySubscribers();
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { n as isPackageInstalled } from "./agent-instructions-BXuKhOzr.js";
|
|
2
|
+
//#region \0@oxc-project+runtime@0.149.0/helpers/esm/usingCtx.js
|
|
3
|
+
function _usingCtx() {
|
|
4
|
+
var r = "function" == typeof SuppressedError ? SuppressedError : function(r, e) {
|
|
5
|
+
var n = Error();
|
|
6
|
+
return n.name = "SuppressedError", n.error = r, n.suppressed = e, n;
|
|
7
|
+
}, e = {}, n = [];
|
|
8
|
+
function using(r, e) {
|
|
9
|
+
if (null != e) {
|
|
10
|
+
if (Object(e) !== e) throw new TypeError("using declarations can only be used with objects, functions, null, or undefined.");
|
|
11
|
+
if (r) var o = e[Symbol.asyncDispose || Symbol["for"]("Symbol.asyncDispose")];
|
|
12
|
+
if (void 0 === o && (o = e[Symbol.dispose || Symbol["for"]("Symbol.dispose")], r)) var t = o;
|
|
13
|
+
if ("function" != typeof o) throw new TypeError("Object is not disposable.");
|
|
14
|
+
t && (o = function o() {
|
|
15
|
+
try {
|
|
16
|
+
t.call(e);
|
|
17
|
+
} catch (r) {
|
|
18
|
+
return Promise.reject(r);
|
|
19
|
+
}
|
|
20
|
+
}), n.push({
|
|
21
|
+
v: e,
|
|
22
|
+
d: o,
|
|
23
|
+
a: r
|
|
24
|
+
});
|
|
25
|
+
} else r && n.push({
|
|
26
|
+
d: e,
|
|
27
|
+
a: r
|
|
28
|
+
});
|
|
29
|
+
return e;
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
e,
|
|
33
|
+
u: using.bind(null, !1),
|
|
34
|
+
a: using.bind(null, !0),
|
|
35
|
+
d: function d() {
|
|
36
|
+
var o, t = this.e, s = 0;
|
|
37
|
+
function next() {
|
|
38
|
+
for (; o = n.pop();) try {
|
|
39
|
+
if (!o.a && 1 === s) return s = 0, n.push(o), Promise.resolve().then(next);
|
|
40
|
+
if (o.d) {
|
|
41
|
+
var r = o.d.call(o.v);
|
|
42
|
+
if (o.a) return s |= 2, Promise.resolve(r).then(next, err);
|
|
43
|
+
} else s |= 1;
|
|
44
|
+
} catch (r) {
|
|
45
|
+
return err(r);
|
|
46
|
+
}
|
|
47
|
+
if (1 === s) return t !== e ? Promise.reject(t) : Promise.resolve();
|
|
48
|
+
if (t !== e) throw t;
|
|
49
|
+
}
|
|
50
|
+
function err(n) {
|
|
51
|
+
return t = t !== e ? new r(n, t) : n, next();
|
|
52
|
+
}
|
|
53
|
+
return next();
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/compat/skills.ts
|
|
59
|
+
/** Never infer authority from cwd, a preset key, or a lookalike Agent. */
|
|
60
|
+
function skillAgent(ctx, options) {
|
|
61
|
+
const scope = options.scope;
|
|
62
|
+
if (typeof scope !== "object" || scope === null || !("id" in scope) || typeof scope.id !== "string") return void 0;
|
|
63
|
+
const agent = ctx.get("agents")?.get(scope.id);
|
|
64
|
+
return agent === scope ? agent : void 0;
|
|
65
|
+
}
|
|
66
|
+
function skillOrigin(ctx, options) {
|
|
67
|
+
const cwd = skillAgent(ctx, options)?.session.header.cwd;
|
|
68
|
+
return typeof cwd === "string" && cwd !== "" ? cwd : void 0;
|
|
69
|
+
}
|
|
70
|
+
/** Absence is optional; a present but broken module must fail visibly. */
|
|
71
|
+
async function filesystemSkillsApi() {
|
|
72
|
+
if (!isPackageInstalled("@deepseek-ai/dsh-skill-filesystem")) return void 0;
|
|
73
|
+
const api = await import("@deepseek-ai/dsh-skill-filesystem");
|
|
74
|
+
if (typeof api.FileSystemSkillProvider !== "function") throw new Error("multi-root skills: FileSystemSkillProvider is unavailable");
|
|
75
|
+
return api;
|
|
76
|
+
}
|
|
77
|
+
function volatileField(schema) {
|
|
78
|
+
const method = schema.volatile;
|
|
79
|
+
return typeof method === "function" ? method.call(schema) : schema;
|
|
80
|
+
}
|
|
81
|
+
function configValue(value) {
|
|
82
|
+
if (typeof value === "object" && value !== null && Symbol.for("cosmokit.volatile.write") in value) {
|
|
83
|
+
const get = value.get;
|
|
84
|
+
if (typeof get === "function") return get.call(value);
|
|
85
|
+
}
|
|
86
|
+
return value;
|
|
87
|
+
}
|
|
88
|
+
/** Cold Browser Catalog supplies a Session header, never a fabricated Agent. */
|
|
89
|
+
async function observedSkillOrigin(ctx, sessionId) {
|
|
90
|
+
try {
|
|
91
|
+
var _usingCtx$1 = _usingCtx();
|
|
92
|
+
const query = ctx.get("sessionQuery");
|
|
93
|
+
if (query === void 0) throw new Error("multi-root skills: SessionQuery is unavailable");
|
|
94
|
+
const cwd = _usingCtx$1.u(await query.observeSession(sessionId)).header.cwd;
|
|
95
|
+
if (typeof cwd !== "string" || cwd === "") throw new Error("multi-root skills: Session has no origin");
|
|
96
|
+
return cwd;
|
|
97
|
+
} catch (_) {
|
|
98
|
+
_usingCtx$1.e = _;
|
|
99
|
+
} finally {
|
|
100
|
+
_usingCtx$1.d();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
//#endregion
|
|
104
|
+
export { skillOrigin as a, skillAgent as i, filesystemSkillsApi as n, volatileField as o, observedSkillOrigin as r, configValue as t };
|