@dsh-electron/dsh-plugin-multi-root-workspace 0.1.8 → 0.1.10
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 +22 -1
- package/CHANGELOG.md +22 -1
- package/README.en.md +39 -16
- package/README.md +47 -24
- package/cordis.patch.yml +10 -0
- package/lib/auto-review-packages-nRjnb4pU.js +15 -0
- package/lib/auto-review.js +166 -0
- package/lib/client.js +573 -131
- package/lib/client.js.map +1 -1
- package/lib/{command-CESuwlVt.js → command-BGdz7VBH.js} +68 -8
- package/lib/command.js +1 -1
- package/lib/{compat-DR2LvmYZ.js → compat-D-MljsPV.js} +13 -2
- package/lib/compat.js +1 -1
- package/lib/{contract-DZ4zczDT.js → contract-CStSEOcK.js} +44 -1
- package/lib/fork-CzbeWxHp.js +670 -0
- package/lib/fs.js +1 -1
- package/lib/index.js +9 -8
- package/lib/{instructions-D19jRxN0.js → instructions-MePoRigy.js} +213 -263
- package/lib/instructions.js +1 -1
- package/lib/llm-message-fYORacvh.js +206 -0
- package/lib/lsp.js +1 -1
- package/lib/metadata-DgqSdVX_.js +35 -0
- package/lib/{registry-DCxC5Lay.js → registry-KSsmU393.js} +66 -13
- package/lib/registry.js +1 -1
- package/lib/review-context-AH-SXHmj.js +98 -0
- package/lib/review-context.js +2 -0
- package/lib/{roots-B-0l8mNG.js → roots-Ca_W1S9n.js} +1 -1
- package/lib/{sandbox-CVAcT_gy.js → sandbox-CXk5h-ct.js} +3 -3
- package/lib/sandbox.js +1 -1
- package/lib/scope.js +15 -6
- package/lib/skills.js +790 -0
- package/lib/types/auto-review/entry.d.ts +5 -0
- package/lib/types/client/WorkspaceFoldersAction.d.ts +1 -1
- package/lib/types/client/locales.d.ts +26 -16
- package/lib/types/client/skills-client.d.ts +23 -0
- package/lib/types/client/skills-settings.d.ts +10 -0
- package/lib/types/client/skills.d.ts +36 -0
- package/lib/types/client/styles.d.ts +1 -1
- package/lib/types/command.d.ts +1 -1
- package/lib/types/compat/auto-review-contract.d.ts +3 -0
- package/lib/types/compat/auto-review-loader.d.ts +33 -0
- package/lib/types/compat/auto-review-packages.d.ts +1 -0
- package/lib/types/compat/client-session.d.ts +2 -0
- package/lib/types/compat/dsh-version.d.ts +5 -5
- package/lib/types/compat/instruction-activity.d.ts +17 -0
- package/lib/types/compat/review-session.d.ts +2 -0
- package/lib/types/compat/skill-content.d.ts +3 -0
- package/lib/types/compat/skills-client.d.ts +33 -0
- package/lib/types/compat/skills-settings.d.ts +8 -0
- package/lib/types/compat/skills.d.ts +13 -0
- package/lib/types/compat/storage-zod.d.ts +1 -0
- package/lib/types/contract.d.ts +12 -1
- package/lib/types/instructions.d.ts +18 -1
- package/lib/types/registry.d.ts +7 -0
- package/lib/types/review-context.d.ts +49 -0
- package/lib/types/roots.d.ts +4 -1
- package/lib/types/scope.d.ts +5 -0
- package/lib/types/skills/catalog.d.ts +34 -0
- package/lib/types/skills/invocation.d.ts +7 -0
- package/lib/types/skills/metadata.d.ts +13 -0
- package/lib/types/skills/pool.d.ts +19 -0
- package/lib/types/skills/reader.d.ts +3 -0
- package/lib/types/skills/routing.d.ts +2 -0
- package/lib/types/skills-contract.d.ts +39 -0
- package/lib/types/skills.d.ts +43 -0
- package/lib/windows-runner.js +2 -2
- package/lib/workspace-files.js +2 -2
- package/package.json +121 -68
- package/src/auto-review/FORK.md +26 -0
- package/src/auto-review/LICENSE +21 -0
- package/src/auto-review/README.en.md +22 -0
- package/src/auto-review/README.md +22 -0
- package/lib/{fs-Nlp2AiRX.js → fs-BTXySwSG.js} +1 -1
- package/lib/{registry-lease-win32-DTO6QpT9.js → registry-lease-win32-DVTq9d4D.js} +1 -1
- package/lib/{windows-runner-contract-CEfmZzu0.js → windows-runner-contract-B5R2K3mM.js} +1 -1
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
//#region src/compat/agent-instructions.ts
|
|
3
|
+
/**
|
|
4
|
+
* Compatibility adapter for upstream's instruction discovery and rendering.
|
|
5
|
+
*
|
|
6
|
+
* `@deepseek-ai/dsh-agent-instructions` renamed its renderer between two
|
|
7
|
+
* releases this plugin supports, without changing its signature:
|
|
8
|
+
*
|
|
9
|
+
* ```text
|
|
10
|
+
* 0.1.5-rc.2 renderWorkspaceContext(files, options) RenderedWorkspaceContext
|
|
11
|
+
* 0.1.6-alpha.1 renderAgentInstructions(files, options) RenderedAgentInstructions
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* `discoverBaselineInstructionFiles` kept both its name and its signature.
|
|
15
|
+
*
|
|
16
|
+
* All coupling to that package is concentrated here so the business layer
|
|
17
|
+
* (`src/instructions.ts`) only ever calls {@link renderInstructions} and
|
|
18
|
+
* {@link InstructionsApi.discover}. A future rename is one edit in this file,
|
|
19
|
+
* not a version check sprinkled through the provider.
|
|
20
|
+
*
|
|
21
|
+
* The export is picked by NAME rather than by comparing the release string:
|
|
22
|
+
* a structural probe also copes with upstream keeping both names for a release,
|
|
23
|
+
* or reshaping within one, which a version comparison does not.
|
|
24
|
+
*
|
|
25
|
+
* The package is loaded LAZILY, and its genuine ABSENCE is reported as absence
|
|
26
|
+
* rather than thrown. It is an optional peer: the default DSH bundle mounts it,
|
|
27
|
+
* but a minimal composition legitimately has neither it nor an agent, and in
|
|
28
|
+
* that case this plugin's instruction row must simply contribute nothing. A
|
|
29
|
+
* package that is present but cannot be evaluated is a different answer, and
|
|
30
|
+
* one this adapter reports instead of caching as absence.
|
|
31
|
+
*
|
|
32
|
+
* @module @dsh-electron/dsh-plugin-multi-root-workspace/compat/agent-instructions
|
|
33
|
+
*/
|
|
34
|
+
/** Raised when the package is present but carries neither renderer name. */
|
|
35
|
+
var DshInstructionApiError = class extends Error {
|
|
36
|
+
constructor(message) {
|
|
37
|
+
super(message);
|
|
38
|
+
this.name = "DshInstructionApiError";
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
/** Resolution is attempted once per process; `null` records a confirmed absence. */
|
|
42
|
+
let resolution;
|
|
43
|
+
/**
|
|
44
|
+
* Pick the renderer this release exports.
|
|
45
|
+
* @param module - the loaded upstream module.
|
|
46
|
+
* @returns the renderer.
|
|
47
|
+
* @throws {DshInstructionApiError} when neither name is present.
|
|
48
|
+
*/
|
|
49
|
+
function selectRenderer(module) {
|
|
50
|
+
const render = module.renderAgentInstructions ?? module.renderWorkspaceContext;
|
|
51
|
+
if (render === void 0) throw new DshInstructionApiError("multi-root workspace: @deepseek-ai/dsh-agent-instructions exports neither renderAgentInstructions (0.1.6 and later) nor renderWorkspaceContext (0.1.5); the additional roots' instruction files cannot be rendered. Adapt src/compat/agent-instructions.ts to the installed release.");
|
|
52
|
+
return render;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Adapt one loaded upstream module to {@link InstructionsApi}.
|
|
56
|
+
* @param module - the loaded upstream module.
|
|
57
|
+
* @returns the adapted surface.
|
|
58
|
+
* @throws {DshInstructionApiError} when a function this plugin needs is absent.
|
|
59
|
+
*/
|
|
60
|
+
function adaptInstructionsModule(module) {
|
|
61
|
+
const discover = module.discoverBaselineInstructionFiles;
|
|
62
|
+
if (discover === void 0) throw new DshInstructionApiError("multi-root workspace: @deepseek-ai/dsh-agent-instructions does not export discoverBaselineInstructionFiles; adapt src/compat/agent-instructions.ts to the installed release.");
|
|
63
|
+
return {
|
|
64
|
+
discover: (options) => discover(options),
|
|
65
|
+
render: selectRenderer(module)
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Whether one package is installed at all, judged WITHOUT evaluating it.
|
|
70
|
+
*
|
|
71
|
+
* This is what separates "the optional peer is absent" from "the optional peer
|
|
72
|
+
* is installed but its evaluation fails" (a missing transitive dependency, a
|
|
73
|
+
* broken build). Only a genuine resolvability failure counts as absence: a
|
|
74
|
+
* resolution that fails for another reason (an `exports` map with no `require`
|
|
75
|
+
* condition, say) leaves the verdict to the `import()` below, which is the
|
|
76
|
+
* authority on whether the module can actually be loaded.
|
|
77
|
+
* @param specifier - the bare package specifier to probe.
|
|
78
|
+
* @returns whether the package resolves from this module.
|
|
79
|
+
*/
|
|
80
|
+
function isPackageInstalled(specifier) {
|
|
81
|
+
try {
|
|
82
|
+
createRequire(import.meta.url).resolve(specifier);
|
|
83
|
+
return true;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
const code = error?.code;
|
|
86
|
+
return code !== "MODULE_NOT_FOUND" && code !== "ERR_MODULE_NOT_FOUND";
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Load and adapt the upstream instruction surface, once.
|
|
91
|
+
*
|
|
92
|
+
* @returns the adapted surface, or `undefined` when the package is not
|
|
93
|
+
* installed. A package that IS installed but exports neither renderer throws
|
|
94
|
+
* instead: that is a compatibility break to fix, not an optional seam. So
|
|
95
|
+
* does one that is installed but cannot be evaluated at all — a missing
|
|
96
|
+
* transitive dependency, a broken build — because reporting that as absence
|
|
97
|
+
* would drop every additional root's instructions for the rest of the
|
|
98
|
+
* process. See {@link isPackageInstalled} for how the two are told apart.
|
|
99
|
+
*/
|
|
100
|
+
async function instructionsApi() {
|
|
101
|
+
resolution ??= (async () => {
|
|
102
|
+
if (!isPackageInstalled("@deepseek-ai/dsh-agent-instructions")) return null;
|
|
103
|
+
return adaptInstructionsModule(await import("@deepseek-ai/dsh-agent-instructions"));
|
|
104
|
+
})();
|
|
105
|
+
return await resolution ?? void 0;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Render loaded instruction files within a byte budget, through whichever
|
|
109
|
+
* renderer the installed release exports.
|
|
110
|
+
*
|
|
111
|
+
* This is the only rendering entry point the business layer uses.
|
|
112
|
+
* @param files - loaded files, ordered broadest to most specific.
|
|
113
|
+
* @param options - the byte budget.
|
|
114
|
+
* @returns the rendered text and its budget diagnostics.
|
|
115
|
+
* @throws {DshInstructionApiError} when no usable renderer is installed.
|
|
116
|
+
*/
|
|
117
|
+
async function renderInstructions(files, options) {
|
|
118
|
+
const api = await instructionsApi();
|
|
119
|
+
if (api === void 0) throw new DshInstructionApiError("multi-root workspace: @deepseek-ai/dsh-agent-instructions is not installed, so instruction files cannot be rendered. Check for its presence with instructionsApi() before calling renderInstructions().");
|
|
120
|
+
return api.render(files, options);
|
|
121
|
+
}
|
|
122
|
+
//#endregion
|
|
123
|
+
//#region src/compat/llm-message.ts
|
|
124
|
+
/**
|
|
125
|
+
* This plugin's own producer kind.
|
|
126
|
+
*
|
|
127
|
+
* Session format 4 rejects the shared `kind: 'plugin'` (`format v4 message
|
|
128
|
+
* requires a producer-owned source kind`). The kind is ours, not
|
|
129
|
+
* `agent-instructions`: upstream treats that kind's `changes` array as its
|
|
130
|
+
* own reconciliation authority.
|
|
131
|
+
*/
|
|
132
|
+
const PRODUCER_SOURCE_KIND = "multi-root-workspace";
|
|
133
|
+
/**
|
|
134
|
+
* The first session format whose encoder refuses `kind: 'plugin'`.
|
|
135
|
+
* Earlier formats declare that kind on `MessageSourceMap` and require it.
|
|
136
|
+
*/
|
|
137
|
+
const PRODUCER_OWNED_FORMAT = 4;
|
|
138
|
+
/**
|
|
139
|
+
* Which source kind the installed session format will persist.
|
|
140
|
+
* @param formatVersion - `SESSION_FORMAT_VERSION`, when the session package is installed.
|
|
141
|
+
* @returns `plugin` through format 3, and this plugin's own kind from format 4 on.
|
|
142
|
+
*/
|
|
143
|
+
function instructionSourceKind(formatVersion) {
|
|
144
|
+
return formatVersion !== void 0 && formatVersion >= PRODUCER_OWNED_FORMAT ? PRODUCER_SOURCE_KIND : "plugin";
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* The installed session format, when that package is present.
|
|
148
|
+
*
|
|
149
|
+
* A composition that never persists a session can omit the package; format 3
|
|
150
|
+
* is then the spelling that still declares `kind: 'plugin'`. Any other failure
|
|
151
|
+
* is a broken install and must surface.
|
|
152
|
+
* @returns the format version, or `undefined` when the package is not installed.
|
|
153
|
+
*/
|
|
154
|
+
async function installedFormatVersion() {
|
|
155
|
+
try {
|
|
156
|
+
const session = await import("@deepseek-ai/dsh-session");
|
|
157
|
+
return typeof session.SESSION_FORMAT_VERSION === "number" ? session.SESSION_FORMAT_VERSION : void 0;
|
|
158
|
+
} catch (error) {
|
|
159
|
+
const code = error.code;
|
|
160
|
+
if (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND") return void 0;
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Build one user-role instruction message.
|
|
166
|
+
*
|
|
167
|
+
* Format 3 and earlier: `{ kind: 'plugin', plugin, form: 'instructions' }`.
|
|
168
|
+
* Format 4 and later: `{ kind: 'multi-root-workspace', plugin, form: 'instructions' }`.
|
|
169
|
+
* @param input - the body and the plugin attribution.
|
|
170
|
+
* @returns the frozen message upstream's helper produced.
|
|
171
|
+
* @throws {Error} when the optional peer is absent: a step that has instruction
|
|
172
|
+
* text to deliver cannot be served without it, and staying silent would drop
|
|
173
|
+
* user-visible context instead of reporting a misconfigured composition.
|
|
174
|
+
*/
|
|
175
|
+
async function createInstructionMessage(input) {
|
|
176
|
+
let module;
|
|
177
|
+
try {
|
|
178
|
+
module = await import("@deepseek-ai/dsh-llm");
|
|
179
|
+
} catch (error) {
|
|
180
|
+
throw new Error(`multi-root workspace: @deepseek-ai/dsh-llm is not installed, so additional roots' instruction files cannot be delivered as messages (${error instanceof Error ? error.message : String(error)}).`);
|
|
181
|
+
}
|
|
182
|
+
const create = module.createUserMessage;
|
|
183
|
+
return create({
|
|
184
|
+
content: [{
|
|
185
|
+
type: "text",
|
|
186
|
+
text: input.text
|
|
187
|
+
}],
|
|
188
|
+
source: instructionSource(input.plugin, await installedFormatVersion())
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
/** The source object the installed format will persist. */
|
|
192
|
+
function instructionSource(plugin, formatVersion) {
|
|
193
|
+
const kind = instructionSourceKind(formatVersion);
|
|
194
|
+
if (kind === "plugin") return {
|
|
195
|
+
kind,
|
|
196
|
+
plugin,
|
|
197
|
+
form: "instructions"
|
|
198
|
+
};
|
|
199
|
+
return {
|
|
200
|
+
kind,
|
|
201
|
+
plugin,
|
|
202
|
+
form: "instructions"
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
//#endregion
|
|
206
|
+
export { renderInstructions as i, instructionsApi as n, isPackageInstalled as r, createInstructionMessage as t };
|
package/lib/lsp.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { t as wrapMethod } from "./method-wrapper-CFY3p3j2.js";
|
|
1
2
|
import { t as aroundTool } from "./tool-context-CpgIFcCB.js";
|
|
2
3
|
import { t as routeWorkspacePath } from "./workspace-routing-YPxaQ4V3.js";
|
|
3
|
-
import { t as wrapMethod } from "./method-wrapper-CFY3p3j2.js";
|
|
4
4
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
5
5
|
//#region src/lsp.ts
|
|
6
6
|
const inject = [
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { t as RootValidationError } from "./roots-Ca_W1S9n.js";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { basename } from "node:path";
|
|
4
|
+
//#region src/skills/metadata.ts
|
|
5
|
+
/** Registry metadata only; this module never resolves or grants a directory. */
|
|
6
|
+
function validAliasId(value) {
|
|
7
|
+
return typeof value === "string" && value.length <= 48 && /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value);
|
|
8
|
+
}
|
|
9
|
+
function skillsMetadataValid(row, rows) {
|
|
10
|
+
return validAliasId(row.aliasId) && typeof row.skillsEnabled === "boolean" && rows.filter((other) => other.aliasId === row.aliasId).length === 1;
|
|
11
|
+
}
|
|
12
|
+
function validateAliasId(value, others) {
|
|
13
|
+
if (!validAliasId(value) || others.some((row) => row.aliasId === value)) throw new RootValidationError("invalid-alias-id", "aliasId must be unique kebab-case, between 1 and 48 characters");
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
/** Missing metadata is legacy; malformed supplied metadata is preserved and withheld. */
|
|
17
|
+
function migrateRootSkills(rows) {
|
|
18
|
+
const used = new Set(rows.flatMap((row) => validAliasId(row.aliasId) ? [row.aliasId] : []));
|
|
19
|
+
const generated = /* @__PURE__ */ new Map();
|
|
20
|
+
for (const row of [...rows].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0)) {
|
|
21
|
+
if (row.aliasId !== void 0) continue;
|
|
22
|
+
const base = (row.alias || basename(row.path)).normalize("NFKD").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 32).replace(/-$/g, "") || "root";
|
|
23
|
+
const aliasId = used.has(base) ? `${base}-${createHash("sha256").update(row.id).digest("hex").slice(0, 12)}` : base;
|
|
24
|
+
if (used.has(aliasId)) continue;
|
|
25
|
+
used.add(aliasId);
|
|
26
|
+
generated.set(row, aliasId);
|
|
27
|
+
}
|
|
28
|
+
return rows.map((row) => ({
|
|
29
|
+
...row,
|
|
30
|
+
...row.aliasId === void 0 && generated.has(row) ? { aliasId: generated.get(row) } : {},
|
|
31
|
+
...row.skillsEnabled === void 0 ? { skillsEnabled: row.readOnlyRecordedPath === void 0 && (row.aliasId === void 0 || validAliasId(row.aliasId) && rows.filter((other) => other.aliasId === row.aliasId).length === 1) } : {}
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
export { skillsMetadataValid as n, validateAliasId as r, migrateRootSkills as t };
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { i as isWindowsNetworkOrDevicePath } from "./windows-runner-contract-
|
|
2
|
-
import { a as classifyStoredRoots, d as reservedRootBoundary, f as resolveRootIndex, i as canonicalRoot, m as validateRootCandidate, n as additionalRootId, r as availableRoots, s as expandRootInput, t as RootValidationError, u as removeStatusAt } from "./roots-
|
|
1
|
+
import { i as isWindowsNetworkOrDevicePath } from "./windows-runner-contract-B5R2K3mM.js";
|
|
2
|
+
import { a as classifyStoredRoots, d as reservedRootBoundary, f as resolveRootIndex, i as canonicalRoot, m as validateRootCandidate, n as additionalRootId, r as availableRoots, s as expandRootInput, t as RootValidationError, u as removeStatusAt } from "./roots-Ca_W1S9n.js";
|
|
3
|
+
import { r as validateAliasId, t as migrateRootSkills } from "./metadata-DgqSdVX_.js";
|
|
3
4
|
import { createRequire } from "node:module";
|
|
4
|
-
import { Service } from "@deepseek-ai/cordis";
|
|
5
5
|
import { closeSync, fstatSync, mkdirSync, openSync, statSync } from "node:fs";
|
|
6
|
-
import { dirname, isAbsolute } from "node:path";
|
|
7
6
|
import { randomUUID } from "node:crypto";
|
|
7
|
+
import { dirname, isAbsolute } from "node:path";
|
|
8
|
+
import { Service } from "@deepseek-ai/cordis";
|
|
8
9
|
import { defineDomain, domainTable } from "@deepseek-ai/dsh-storage-domain";
|
|
9
10
|
import { tryLockExclusive } from "@deepseek-ai/node-addon-system/flock";
|
|
10
11
|
//#region src/compat/storage-zod.ts
|
|
@@ -123,7 +124,7 @@ var RegistryAuthorityLease = class RegistryAuthorityLease {
|
|
|
123
124
|
mode: 448
|
|
124
125
|
});
|
|
125
126
|
if (process.platform === "win32") {
|
|
126
|
-
const { acquireLockHandleWin32 } = await import("./registry-lease-win32-
|
|
127
|
+
const { acquireLockHandleWin32 } = await import("./registry-lease-win32-DVTq9d4D.js");
|
|
127
128
|
let handle;
|
|
128
129
|
try {
|
|
129
130
|
handle = await acquireLockHandleWin32(leasePath);
|
|
@@ -175,7 +176,7 @@ var RegistryAuthorityLease = class RegistryAuthorityLease {
|
|
|
175
176
|
if (this.released) return;
|
|
176
177
|
this.released = true;
|
|
177
178
|
if (this.held.kind === "win32") {
|
|
178
|
-
const { releaseLockHandleWin32 } = await import("./registry-lease-win32-
|
|
179
|
+
const { releaseLockHandleWin32 } = await import("./registry-lease-win32-DVTq9d4D.js");
|
|
179
180
|
await releaseLockHandleWin32(this.held.handle);
|
|
180
181
|
return;
|
|
181
182
|
}
|
|
@@ -268,6 +269,8 @@ const persistedRoot = z.object({
|
|
|
268
269
|
readOnlyRecordedPath: z.string().min(1).optional(),
|
|
269
270
|
readWriteRecordedPath: z.string().min(1).optional(),
|
|
270
271
|
alias: z.string().optional(),
|
|
272
|
+
aliasId: z.unknown().optional(),
|
|
273
|
+
skillsEnabled: z.unknown().optional(),
|
|
271
274
|
addedAt: z.string().min(1)
|
|
272
275
|
});
|
|
273
276
|
const persistedPrimaryRoot = z.object({ roots: z.array(persistedRoot) });
|
|
@@ -469,6 +472,7 @@ var MultiRootRegistry = class extends Service {
|
|
|
469
472
|
id: additionalRootId(randomUUID()),
|
|
470
473
|
path: canonical,
|
|
471
474
|
access,
|
|
475
|
+
skillsEnabled: access !== "read-only",
|
|
472
476
|
...grantFields(access, canonical),
|
|
473
477
|
state: "available",
|
|
474
478
|
...alias === void 0 ? {} : { alias },
|
|
@@ -477,6 +481,7 @@ var MultiRootRegistry = class extends Service {
|
|
|
477
481
|
...dropGrantFields(status),
|
|
478
482
|
path: canonical,
|
|
479
483
|
access: input.access ?? status.access,
|
|
484
|
+
...input.access !== void 0 && input.access !== status.access ? { skillsEnabled: input.access !== "read-only" } : {},
|
|
480
485
|
...grantFields(input.access ?? status.access, canonical),
|
|
481
486
|
addedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
482
487
|
...alias === void 0 ? {} : { alias }
|
|
@@ -520,6 +525,7 @@ var MultiRootRegistry = class extends Service {
|
|
|
520
525
|
const next = statuses.map((status, index) => index === targetIndex ? {
|
|
521
526
|
...dropGrantFields(status),
|
|
522
527
|
access,
|
|
528
|
+
skillsEnabled: access !== "read-only",
|
|
523
529
|
...recorded === void 0 ? {} : grantFields(access, recorded)
|
|
524
530
|
} : status);
|
|
525
531
|
return await this.persist(key, next);
|
|
@@ -579,6 +585,28 @@ var MultiRootRegistry = class extends Service {
|
|
|
579
585
|
return await this.persist(key, next);
|
|
580
586
|
});
|
|
581
587
|
}
|
|
588
|
+
/** Change a Skill switch without changing any grant field. */
|
|
589
|
+
async setSkillsEnabled(primaryRoot, ref, skillsEnabled) {
|
|
590
|
+
if (typeof skillsEnabled !== "boolean") throw new RootValidationError("invalid-ref", "skillsEnabled must be a boolean");
|
|
591
|
+
return await this.setSkillsMetadata(primaryRoot, ref, { skillsEnabled });
|
|
592
|
+
}
|
|
593
|
+
async setAliasId(primaryRoot, ref, aliasId) {
|
|
594
|
+
return await this.setSkillsMetadata(primaryRoot, ref, { aliasId });
|
|
595
|
+
}
|
|
596
|
+
async setSkillsMetadata(primaryRoot, ref, patch) {
|
|
597
|
+
await this.ensureAuthority();
|
|
598
|
+
this.requireActive();
|
|
599
|
+
const key = canonicalRoot(primaryRoot);
|
|
600
|
+
return await this.serialize(key, async () => {
|
|
601
|
+
const statuses = this.reclassify(key);
|
|
602
|
+
const target = resolveRootIndex(statuses, ref);
|
|
603
|
+
if (patch.aliasId !== void 0) validateAliasId(patch.aliasId, statuses.filter((_row, index) => index !== target));
|
|
604
|
+
return await this.persist(key, statuses.map((row, index) => index === target ? {
|
|
605
|
+
...row,
|
|
606
|
+
...patch
|
|
607
|
+
} : row));
|
|
608
|
+
});
|
|
609
|
+
}
|
|
582
610
|
/**
|
|
583
611
|
* Move one registration within the display order.
|
|
584
612
|
* @param primaryRoot - the session workspace root the root belongs to.
|
|
@@ -705,12 +733,10 @@ var MultiRootRegistry = class extends Service {
|
|
|
705
733
|
*/
|
|
706
734
|
async persist(key, statuses) {
|
|
707
735
|
const table = this.requireTable();
|
|
736
|
+
const durable = persistedPrimaryRoot.parse({ roots: migrateRootSkills(statuses.map(toPersistedRoot)) });
|
|
708
737
|
if (statuses.length === 0) await table.delete(key);
|
|
709
|
-
else
|
|
710
|
-
|
|
711
|
-
await table.put(key, durable);
|
|
712
|
-
}
|
|
713
|
-
const classified = statuses.length === 0 ? [] : classifyStoredRoots(key, statuses.map(toRegisteredRoot));
|
|
738
|
+
else await table.put(key, durable);
|
|
739
|
+
const classified = statuses.length === 0 ? [] : classifyStoredRoots(key, durable.roots.map(toRegisteredRoot));
|
|
714
740
|
this.cache.set(key, classified);
|
|
715
741
|
this.publish(key, classified);
|
|
716
742
|
return classified;
|
|
@@ -750,10 +776,22 @@ var MultiRootRegistry = class extends Service {
|
|
|
750
776
|
* under a shared byte budget, so a move must update the live scope immediately.
|
|
751
777
|
* Aliases and no-op moves still leave the signature unchanged.
|
|
752
778
|
*/
|
|
779
|
+
metadataPublished = /* @__PURE__ */ new Map();
|
|
753
780
|
publish(key, statuses) {
|
|
754
781
|
const withheld = statuses.filter((status) => status.state !== "available");
|
|
755
782
|
const signature = [...statuses.filter((status) => status.state === "available").map((status) => `available:${status.access}:${recordedGrantOf(status)}`), ...withheld.map((status) => `${status.state}:${status.invalidReason ?? ""}:${status.access}:${status.path}:${recordedGrantOf(status) ?? ""}`)].join("\n");
|
|
756
|
-
|
|
783
|
+
const previousMetadata = this.metadataPublished.get(key);
|
|
784
|
+
const metadata = JSON.stringify(statuses.map((row) => [
|
|
785
|
+
row.id,
|
|
786
|
+
row.aliasId,
|
|
787
|
+
row.skillsEnabled,
|
|
788
|
+
row.state
|
|
789
|
+
]));
|
|
790
|
+
this.metadataPublished.set(key, metadata);
|
|
791
|
+
if ((this.published.get(key) ?? "") === signature) {
|
|
792
|
+
if (previousMetadata !== metadata && (previousMetadata !== void 0 || statuses.length > 0)) for (const listener of this.listeners) listener(key);
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
757
795
|
this.published.set(key, signature);
|
|
758
796
|
const effective = statuses.flatMap((status) => {
|
|
759
797
|
const recorded = recordedGrantOf(status);
|
|
@@ -851,7 +889,17 @@ var MultiRootRegistry = class extends Service {
|
|
|
851
889
|
this.loggedContended = false;
|
|
852
890
|
this.cache.clear();
|
|
853
891
|
this.published.clear();
|
|
854
|
-
for (const [primaryRoot, record] of this.table.entries()) this.
|
|
892
|
+
for (const [primaryRoot, record] of this.table.entries()) await this.serialize(canonicalRoot(primaryRoot), async () => {
|
|
893
|
+
const migrated = { roots: migrateRootSkills(record.roots) };
|
|
894
|
+
let accepted = record;
|
|
895
|
+
if (JSON.stringify(migrated) !== JSON.stringify(record)) try {
|
|
896
|
+
await this.table.put(primaryRoot, migrated);
|
|
897
|
+
accepted = migrated;
|
|
898
|
+
} catch (error) {
|
|
899
|
+
this.ctx.logger.warn(`multi-root skills: metadata migration failed; Skills remain withheld: ${String(error)}`);
|
|
900
|
+
}
|
|
901
|
+
this.accept(primaryRoot, accepted);
|
|
902
|
+
});
|
|
855
903
|
this.ctx.multiRootScope.setRegistryAuthority(true);
|
|
856
904
|
}
|
|
857
905
|
/** Fail closed: empty scope, no table, contended state. */
|
|
@@ -879,6 +927,7 @@ var MultiRootRegistry = class extends Service {
|
|
|
879
927
|
withdrawPublished() {
|
|
880
928
|
for (const key of this.published.keys()) this.ctx.multiRootScope.setAdditionalRoots(key, []);
|
|
881
929
|
this.published.clear();
|
|
930
|
+
this.metadataPublished.clear();
|
|
882
931
|
this.cache.clear();
|
|
883
932
|
}
|
|
884
933
|
/**
|
|
@@ -966,6 +1015,8 @@ function toPersistedRoot(status) {
|
|
|
966
1015
|
path: status.path,
|
|
967
1016
|
...grant,
|
|
968
1017
|
...status.alias === void 0 ? {} : { alias: status.alias },
|
|
1018
|
+
...status.aliasId === void 0 ? {} : { aliasId: status.aliasId },
|
|
1019
|
+
...status.skillsEnabled === void 0 ? {} : { skillsEnabled: status.skillsEnabled },
|
|
969
1020
|
addedAt: status.addedAt
|
|
970
1021
|
};
|
|
971
1022
|
}
|
|
@@ -993,6 +1044,8 @@ function toRegisteredRoot(entry) {
|
|
|
993
1044
|
...entry.readOnlyRecordedPath === void 0 ? {} : { readOnlyRecordedPath: entry.readOnlyRecordedPath },
|
|
994
1045
|
...entry.readWriteRecordedPath === void 0 ? {} : { readWriteRecordedPath: entry.readWriteRecordedPath },
|
|
995
1046
|
...entry.alias === void 0 ? {} : { alias: entry.alias },
|
|
1047
|
+
...entry.aliasId === void 0 ? {} : { aliasId: entry.aliasId },
|
|
1048
|
+
...entry.skillsEnabled === void 0 ? {} : { skillsEnabled: entry.skillsEnabled },
|
|
996
1049
|
addedAt: entry.addedAt
|
|
997
1050
|
};
|
|
998
1051
|
}
|
package/lib/registry.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as MultiRootRegistry, c as multiRootDomainSpec, i as MAX_ALIAS_LENGTH, n as DOMAIN_NAME, o as REGISTRY_CONTENDED_MESSAGE, r as MAX_ADDITIONAL_ROOTS_CEILING, s as TABLE_NAME, t as DEFAULT_MAX_ADDITIONAL_ROOTS } from "./registry-
|
|
1
|
+
import { a as MultiRootRegistry, c as multiRootDomainSpec, i as MAX_ALIAS_LENGTH, n as DOMAIN_NAME, o as REGISTRY_CONTENDED_MESSAGE, r as MAX_ADDITIONAL_ROOTS_CEILING, s as TABLE_NAME, t as DEFAULT_MAX_ADDITIONAL_ROOTS } from "./registry-KSsmU393.js";
|
|
2
2
|
export { DEFAULT_MAX_ADDITIONAL_ROOTS, DOMAIN_NAME, MAX_ADDITIONAL_ROOTS_CEILING, MAX_ALIAS_LENGTH, MultiRootRegistry, MultiRootRegistry as default, REGISTRY_CONTENDED_MESSAGE, TABLE_NAME, multiRootDomainSpec };
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { t as effectiveDirectory } from "./session-directory-BLOstut1.js";
|
|
2
|
+
import { s as touchedPathOfToolCall } from "./instructions-MePoRigy.js";
|
|
3
|
+
import { l as isCanonicallyUnder } from "./roots-Ca_W1S9n.js";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import { resolve } from "node:path";
|
|
6
|
+
import { Service } from "@deepseek-ai/cordis";
|
|
7
|
+
import { canonicalPath } from "@deepseek-ai/dsh-sandbox";
|
|
8
|
+
//#region src/compat/auto-review-contract.ts
|
|
9
|
+
/** The companion's joint support contract is deliberately narrower than the ordinary bundle. */
|
|
10
|
+
const AUTO_REVIEW_RELEASE = "0.2.1-rc.2";
|
|
11
|
+
function assertAutoReviewRelease(release) {
|
|
12
|
+
if (release !== "0.2.1-rc.2") throw new Error(`multi-root Auto Review requires exactly DSH ${AUTO_REVIEW_RELEASE}`);
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/compat/review-session.ts
|
|
16
|
+
/** Fingerprint only the native instruction producer's visible authority data. */
|
|
17
|
+
function nativeInstructionFingerprint(session) {
|
|
18
|
+
const rows = [];
|
|
19
|
+
for (const seq of session.surface.nodes) {
|
|
20
|
+
const event = session.eventAt(seq);
|
|
21
|
+
if (event?.type === "user/message" && event.data.source.kind === "agent-instructions") rows.push(event.data);
|
|
22
|
+
}
|
|
23
|
+
return createHash("sha256").update(JSON.stringify(rows)).digest("hex");
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/review-context.ts
|
|
27
|
+
/** Read-only, current host context for the optional Auto Review companion. */
|
|
28
|
+
/** All authority remains in the existing Registry/scope and instruction index. */
|
|
29
|
+
var MultiRootReviewContext = class extends Service {
|
|
30
|
+
static inject = [
|
|
31
|
+
"multiRootCompat",
|
|
32
|
+
"multiRootProviders",
|
|
33
|
+
"multiRootScope",
|
|
34
|
+
"multiRootRegistry",
|
|
35
|
+
"multiRootInstructions",
|
|
36
|
+
"sandboxPolicy",
|
|
37
|
+
"workingDirectory",
|
|
38
|
+
"fs"
|
|
39
|
+
];
|
|
40
|
+
constructor(ctx) {
|
|
41
|
+
super(ctx, "multiRootReviewContext");
|
|
42
|
+
}
|
|
43
|
+
assertCompatible() {
|
|
44
|
+
assertAutoReviewRelease(this.ctx.multiRootCompat.release);
|
|
45
|
+
}
|
|
46
|
+
async build(exec, signal) {
|
|
47
|
+
signal.throwIfAborted();
|
|
48
|
+
const agent = exec.agent;
|
|
49
|
+
if (agent === void 0) throw new Error("review-context: missing session");
|
|
50
|
+
const policy = this.ctx.sandboxPolicy.resolve({ session: agent.session });
|
|
51
|
+
const primary = canonicalPath(policy.workspaceRoot);
|
|
52
|
+
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(primary);
|
|
54
|
+
signal.throwIfAborted();
|
|
55
|
+
const cwd = effectiveDirectory(this.ctx, agent.session);
|
|
56
|
+
if (cwd === void 0) throw new Error("review-context: missing effective cwd");
|
|
57
|
+
const scope = this.ctx.multiRootScope.previewForTool(policy, exec.name);
|
|
58
|
+
const environment = {
|
|
59
|
+
sessionOrigin: scope.primaryRoot,
|
|
60
|
+
effectiveCwd: cwd,
|
|
61
|
+
sandboxMode: policy.mode,
|
|
62
|
+
registryAuthority: scope.authority,
|
|
63
|
+
roots: scope.roots.map((root) => ({
|
|
64
|
+
path: root.path,
|
|
65
|
+
access: root.access,
|
|
66
|
+
effectiveWritable: scope.writableRoots.includes(root.path) && !scope.deniedRoots.some((deny) => isCanonicallyUnder(root.path, deny))
|
|
67
|
+
})),
|
|
68
|
+
writableRoots: scope.writableRoots,
|
|
69
|
+
deniedRoots: scope.deniedRoots,
|
|
70
|
+
...scope.protectedHome === void 0 ? {} : { protectedHome: scope.protectedHome },
|
|
71
|
+
ambientWrites: policy.mode === "danger-full-access" && scope.authority === "available",
|
|
72
|
+
readConfinement: false,
|
|
73
|
+
primaryRootWritable: policy.mode !== "read-only" && !scope.deniedRoots.some((deny) => isCanonicallyUnder(scope.primaryRoot, deny)),
|
|
74
|
+
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
|
+
const path = touchedPathOfToolCall(exec.name, JSON.stringify(exec.arguments));
|
|
77
|
+
const target = path === void 0 ? void 0 : this.ctx.fs.processPath(await this.ctx.fs.resolve(resolve(cwd, path), { signal }));
|
|
78
|
+
const constraints = await this.ctx.multiRootInstructions.snapshot(agent, scope.authority === "available" ? scope.additionalRoots : [], target, signal);
|
|
79
|
+
signal.throwIfAborted();
|
|
80
|
+
const payload = {
|
|
81
|
+
schemaVersion: 1,
|
|
82
|
+
environment,
|
|
83
|
+
instructionProvenanceRoots: [.../* @__PURE__ */ new Set([...scope.additionalRoots, ...this.ctx.multiRootInstructions.knownRoots(agent.session)])],
|
|
84
|
+
constraints,
|
|
85
|
+
completeness: "complete"
|
|
86
|
+
};
|
|
87
|
+
if (Buffer.byteLength(JSON.stringify(payload), "utf8") > 65536) throw new Error("review-context: context budget exceeded");
|
|
88
|
+
return {
|
|
89
|
+
...payload,
|
|
90
|
+
fingerprint: createHash("sha256").update(JSON.stringify([payload, nativeInstructionFingerprint(agent.session)])).digest("hex")
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
async assertCurrent(exec, snapshot, signal) {
|
|
94
|
+
if ((await this.build(exec, signal)).fingerprint !== snapshot.fingerprint) throw new Error("review-context: snapshot changed; retry the tool call");
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
//#endregion
|
|
98
|
+
export { assertAutoReviewRelease as n, MultiRootReviewContext as t };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as isWindowsNetworkOrDevicePath } from "./windows-runner-contract-
|
|
1
|
+
import { i as isWindowsNetworkOrDevicePath } from "./windows-runner-contract-B5R2K3mM.js";
|
|
2
2
|
import { statSync } from "node:fs";
|
|
3
3
|
import { isAbsolute, join, sep } from "node:path";
|
|
4
4
|
import { canonicalPath } from "@deepseek-ai/dsh-sandbox";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { a as parseWindowsProfile, i as isWindowsNetworkOrDevicePath, n as assertWindowsDenyCapacity, t as DialectUnrecognizedError } from "./windows-runner-contract-
|
|
2
|
-
import { l as isCanonicallyUnder } from "./roots-
|
|
1
|
+
import { a as parseWindowsProfile, i as isWindowsNetworkOrDevicePath, n as assertWindowsDenyCapacity, t as DialectUnrecognizedError } from "./windows-runner-contract-B5R2K3mM.js";
|
|
2
|
+
import { l as isCanonicallyUnder } from "./roots-Ca_W1S9n.js";
|
|
3
3
|
import { brandMultiRootProvider } from "./providers.js";
|
|
4
4
|
import { posix } from "node:path";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
5
|
import { SandboxUnavailableError, canonicalPath } from "@deepseek-ai/dsh-sandbox";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { LocalSandboxProvider } from "@deepseek-ai/dsh-sandbox-local";
|
|
8
8
|
//#region src/compat/sandbox-confine.ts
|
|
9
9
|
/** Whether a value is a thenable, i.e. whether the installed `confine` is async. */
|
package/lib/sandbox.js
CHANGED
package/lib/scope.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { t as aroundTool } from "./tool-context-CpgIFcCB.js";
|
|
2
|
-
import { r as effectiveRootAccess } from "./contract-
|
|
3
|
-
import { Service } from "@deepseek-ai/cordis";
|
|
2
|
+
import { r as effectiveRootAccess } from "./contract-CStSEOcK.js";
|
|
4
3
|
import { statSync } from "node:fs";
|
|
5
4
|
import { isAbsolute } from "node:path";
|
|
6
|
-
import {
|
|
5
|
+
import { Service } from "@deepseek-ai/cordis";
|
|
7
6
|
import { canonicalPath } from "@deepseek-ai/dsh-sandbox";
|
|
7
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
8
8
|
//#region src/scope.ts
|
|
9
9
|
/**
|
|
10
10
|
* The multi-root workspace scope: the plugin's single home for the question
|
|
@@ -210,8 +210,7 @@ var MultiRootScopeService = class extends Service {
|
|
|
210
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
211
|
else this.dshHome = dshHome;
|
|
212
212
|
aroundTool(ctx, (exec, next) => {
|
|
213
|
-
|
|
214
|
-
return toolCapStore.run({ capped: parent?.capped === true || this.caps[exec.name] === "read-only" }, next);
|
|
213
|
+
return toolCapStore.run({ capped: this.capForTool(exec.name) }, next);
|
|
215
214
|
});
|
|
216
215
|
ctx.inject(["systemPrompt", "sandboxPolicy"], (scope) => {
|
|
217
216
|
scope.systemPrompt.context({
|
|
@@ -243,9 +242,19 @@ var MultiRootScopeService = class extends Service {
|
|
|
243
242
|
* @returns the canonical scope; `additionalRoots` is empty when none are registered.
|
|
244
243
|
*/
|
|
245
244
|
resolve(policy) {
|
|
245
|
+
return this.project(policy, this.capped());
|
|
246
|
+
}
|
|
247
|
+
/** Read-only pending-dispatch projection. Does not mint an ALS execution grant. */
|
|
248
|
+
previewForTool(policy, toolName) {
|
|
249
|
+
return this.project(policy, this.capForTool(toolName));
|
|
250
|
+
}
|
|
251
|
+
/** Execution and review use exactly the same parent-narrowing rule. */
|
|
252
|
+
capForTool(toolName) {
|
|
253
|
+
return this.capsInvalid || toolCapStore.getStore()?.capped === true || this.caps[toolName] === "read-only";
|
|
254
|
+
}
|
|
255
|
+
project(policy, capped) {
|
|
246
256
|
const primaryRoot = canonicalPath(policy.workspaceRoot);
|
|
247
257
|
const roots = this.membersOf(primaryRoot);
|
|
248
|
-
const capped = this.capped();
|
|
249
258
|
const writable = capped || this.registryAuthority === "unavailable" ? [] : roots.filter((root) => effectivelyWritable(policy.mode, root.access)).map((root) => root.path);
|
|
250
259
|
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
260
|
const deniedRoots = [...new Set(denied)];
|