@dsh-electron/dsh-plugin-multi-root-workspace 0.1.7 → 0.1.9

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.
Files changed (48) hide show
  1. package/CHANGELOG.en.md +16 -1
  2. package/CHANGELOG.md +16 -1
  3. package/README.en.md +21 -13
  4. package/README.md +29 -21
  5. package/cordis.patch.yml +18 -4
  6. package/lib/auto-review-packages-nRjnb4pU.js +15 -0
  7. package/lib/auto-review.js +166 -0
  8. package/lib/{command-CESuwlVt.js → command-Bwd6EGNV.js} +3 -3
  9. package/lib/command.js +1 -1
  10. package/lib/{compat-DF_yL1bB.js → compat-CagUNzRB.js} +176 -29
  11. package/lib/compat.js +1 -1
  12. package/lib/fork-CzbeWxHp.js +670 -0
  13. package/lib/{fs-9JIzGaf3.js → fs-BTXySwSG.js} +2 -6
  14. package/lib/fs.js +1 -1
  15. package/lib/index.js +8 -8
  16. package/lib/{instructions-D19jRxN0.js → instructions-CGNS3H0H.js} +211 -57
  17. package/lib/instructions.js +1 -1
  18. package/lib/lsp.js +1 -1
  19. package/lib/{registry-DCxC5Lay.js → registry-BClm6wfh.js} +6 -6
  20. package/lib/registry.js +1 -1
  21. package/lib/review-context-D-ohSVmA.js +98 -0
  22. package/lib/review-context.js +2 -0
  23. package/lib/{roots-B-0l8mNG.js → roots-Ca_W1S9n.js} +1 -1
  24. package/lib/{sandbox-Kup4_ZIb.js → sandbox-CXk5h-ct.js} +4 -8
  25. package/lib/sandbox.js +1 -1
  26. package/lib/scope.js +14 -5
  27. package/lib/types/auto-review/entry.d.ts +5 -0
  28. package/lib/types/compat/auto-review-contract.d.ts +3 -0
  29. package/lib/types/compat/auto-review-loader.d.ts +33 -0
  30. package/lib/types/compat/auto-review-packages.d.ts +1 -0
  31. package/lib/types/compat/dsh-version.d.ts +56 -8
  32. package/lib/types/compat/instruction-activity.d.ts +17 -0
  33. package/lib/types/compat/pty-marker.d.ts +1 -1
  34. package/lib/types/compat/review-session.d.ts +2 -0
  35. package/lib/types/compat.d.ts +15 -5
  36. package/lib/types/index.d.ts +1 -1
  37. package/lib/types/instructions.d.ts +18 -1
  38. package/lib/types/review-context.d.ts +49 -0
  39. package/lib/types/scope.d.ts +5 -0
  40. package/lib/windows-runner.js +2 -2
  41. package/lib/workspace-files.js +2 -2
  42. package/package.json +47 -7
  43. package/src/auto-review/FORK.md +26 -0
  44. package/src/auto-review/LICENSE +21 -0
  45. package/src/auto-review/README.en.md +22 -0
  46. package/src/auto-review/README.md +22 -0
  47. package/lib/{registry-lease-win32-DTO6QpT9.js → registry-lease-win32-DVTq9d4D.js} +1 -1
  48. package/lib/{windows-runner-contract-CEfmZzu0.js → windows-runner-contract-B5R2K3mM.js} +1 -1
@@ -1,7 +1,8 @@
1
1
  import { createRequire } from "node:module";
2
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
3
+ import { dirname, join, sep } from "node:path";
2
4
  import { Service } from "@deepseek-ai/cordis";
3
- import { readFileSync, readdirSync } from "node:fs";
4
- import { dirname, join } from "node:path";
5
+ import { fileURLToPath } from "node:url";
5
6
  //#region src/compat/dsh-version.ts
6
7
  /**
7
8
  * The DSH compatibility contract: which upstream releases this plugin is
@@ -86,39 +87,175 @@ const OPTIONAL_CORE_PACKAGES = [
86
87
  "@deepseek-ai/dsh-subprocess",
87
88
  "@deepseek-ai/dsh-system-prompt"
88
89
  ];
90
+ /**
91
+ * The optional packages this plugin loads itself at run time, through dynamic
92
+ * `import()`s inside this package's own modules. The copy this module resolves
93
+ * IS the copy that would execute, so every resolvable hit testifies — a
94
+ * private copy inside this package's own tree genuinely loads, and a version
95
+ * that disagrees with the host is a real mixed installation, not a phantom.
96
+ */
97
+ const RUNTIME_OPTIONAL_PACKAGES = [
98
+ "@deepseek-ai/dsh-agent-instructions",
99
+ "@deepseek-ai/dsh-llm",
100
+ "@deepseek-ai/dsh-session",
101
+ "@deepseek-ai/dsh-typert-protocol"
102
+ ];
103
+ /**
104
+ * The optional packages this plugin consumes only as mounted host services or
105
+ * ambient types — it never imports them, so a copy inside this package's own
106
+ * tree can never execute. The only version that testifies is the one the
107
+ * COMPOSITION resolves (the installation or the profile): a resolution that
108
+ * lands inside this package's own root is a private development or bundled
109
+ * copy and must be reported absent — otherwise a `link:`-installed checkout
110
+ * whose devDependencies carry a newer release phantom-mixes every supported
111
+ * host it is developed against (the report that motivated this split:
112
+ * `dsh-lsp`/`dsh-lsp-stdio`/`dsh-working-directory` resolving to the plugin's
113
+ * devDependencies on a `0.2.0-rc.2` host produced a `mixed` verdict on a
114
+ * uniformly `0.2.0-rc.2` installation).
115
+ */
116
+ const HOST_SERVICE_PACKAGES = [
117
+ "@deepseek-ai/dsh-agent",
118
+ "@deepseek-ai/dsh-api-workspace-files",
119
+ "@deepseek-ai/dsh-client-connection",
120
+ "@deepseek-ai/dsh-commands",
121
+ "@deepseek-ai/dsh-lsp",
122
+ "@deepseek-ai/dsh-lsp-stdio",
123
+ "@deepseek-ai/dsh-subprocess",
124
+ "@deepseek-ai/dsh-system-prompt",
125
+ "@deepseek-ai/dsh-tools",
126
+ "@deepseek-ai/dsh-working-directory"
127
+ ];
128
+ /**
129
+ * Optional packages this plugin executes that are resolved through an anchor
130
+ * OTHER than this module. The standalone Windows ACL runner
131
+ * (`src/windows-runner.ts`) imports `@deepseek-ai/dsh-sandbox-windows-acl` by
132
+ * self-reference from the upstream runner's own install location — a file the
133
+ * installed `dsh-sandbox-local` points at in its confined argv — so the copy
134
+ * that testifies is the one sibling to that required package, not whatever
135
+ * this module can reach. Neither the self-resolution hit nor the
136
+ * `@deepseek-ai/dsh` host anchor reflects execution here: if sandbox-local's
137
+ * own anchor cannot see the package, the runner cannot load it either, and
138
+ * absent is the honest answer.
139
+ */
140
+ const RUNNER_ANCHORED_PACKAGES = ["@deepseek-ai/dsh-sandbox-windows-acl"];
141
+ /** The required package whose tree produces the upstream runner argv. */
142
+ const RUNNER_ANCHOR_PACKAGE = "@deepseek-ai/dsh-sandbox-local";
89
143
  /** Every package the contract inspects, required first. */
90
144
  const CORE_PACKAGES = [...REQUIRED_CORE_PACKAGES, ...OPTIONAL_CORE_PACKAGES];
91
- /** Services looked up structurally; older runtimes never install these modules. */
92
- const HOST_RESOLVED_PACKAGES = ["@deepseek-ai/dsh-working-directory"];
93
145
  /** The environment variable that relaxes the gate. */
94
146
  const COMPAT_ENFORCEMENT_ENV = "DSH_MULTI_ROOT_COMPAT";
95
147
  /**
148
+ * Resolve one package's `package.json` path through a require, or `undefined`
149
+ * when it cannot be resolved. The path — not just the version — matters: the
150
+ * caller needs to know WHERE the copy lives to judge whether it belongs to the
151
+ * inspected installation.
152
+ * @param require - the require to resolve through.
153
+ * @param packageName - the package to locate.
154
+ * @returns the manifest path, or `undefined`.
155
+ */
156
+ function resolvePackageJson(require, packageName) {
157
+ try {
158
+ return require.resolve(`${packageName}/package.json`);
159
+ } catch {
160
+ return;
161
+ }
162
+ }
163
+ /** Read `version` off a resolved manifest path; a broken manifest is absent. */
164
+ function versionOfManifest(manifestPath) {
165
+ try {
166
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
167
+ return typeof manifest.version === "string" ? manifest.version : void 0;
168
+ } catch {
169
+ return;
170
+ }
171
+ }
172
+ /**
173
+ * The second resolution anchor: a require rooted at the installed
174
+ * `@deepseek-ai/dsh` meta package. Host-internal packages that never appear as
175
+ * plugin peers are only reachable this way (the pattern the retired
176
+ * `HOST_RESOLVED_PACKAGES` list carved out for `dsh-working-directory`,
177
+ * generalized here to every host-service package).
178
+ * @param require - this module's require, used to locate the meta package.
179
+ * @returns the host-rooted require, or `undefined` when `dsh` itself cannot be
180
+ * resolved.
181
+ */
182
+ function hostAnchorRequire(require) {
183
+ const anchor = resolvePackageJson(require, "@deepseek-ai/dsh");
184
+ return anchor === void 0 ? void 0 : createRequire(anchor);
185
+ }
186
+ /**
187
+ * This package's own install root — the boundary between a copy that belongs
188
+ * to the inspected installation and a private copy only this package can see.
189
+ * @param require - this module's require; self-reference through the package's
190
+ * `exports` is tried first, then a walk up from this module's file.
191
+ * @returns the package root, or `undefined` when neither anchor works.
192
+ */
193
+ function pluginPackageRoot(require) {
194
+ try {
195
+ return dirname(require.resolve("@dsh-electron/dsh-plugin-multi-root-workspace/package.json"));
196
+ } catch {
197
+ let dir = dirname(fileURLToPath(import.meta.url));
198
+ while (true) {
199
+ if (existsSync(join(dir, "package.json"))) return dir;
200
+ const parent = dirname(dir);
201
+ if (parent === dir) return void 0;
202
+ dir = parent;
203
+ }
204
+ }
205
+ }
206
+ /**
207
+ * Whether a resolved manifest path lives inside this package's own root — the
208
+ * mark of a private copy no composition can mount. An unknown root cannot
209
+ * exclude anything, so it answers `false` rather than guessing.
210
+ */
211
+ function insidePackageRoot(manifestPath, root) {
212
+ return root !== void 0 && (manifestPath === root || manifestPath.startsWith(`${root}${sep}`));
213
+ }
214
+ /**
96
215
  * Read one package's declared version through Node resolution.
97
216
  *
98
- * Resolution runs from THIS module, which is what makes the answer meaningful:
99
- * the upstream packages are peer dependencies, so they resolve to the single
100
- * copy the host provides — the same copy the providers will subclass at
101
- * runtime. A package that cannot be resolved is reported absent rather than
102
- * throwing, because "not installed" is a legitimate answer for every optional
103
- * package.
217
+ * Resolution runs from THIS module — for required and runtime-loaded packages
218
+ * the copy found here is the copy that would execute, which is what makes the
219
+ * answer meaningful: the upstream packages are peer dependencies, so they
220
+ * resolve to the single copy the host provides — the same copy the providers
221
+ * will subclass at runtime.
222
+ *
223
+ * {@link HOST_SERVICE_PACKAGES} are different: the plugin never imports them,
224
+ * so a copy inside this package's own tree can never execute, and counting it
225
+ * would report a mixed installation out of a linked checkout's devDependencies
226
+ * alone. Only a hit OUTSIDE this package's root — a copy the composition could
227
+ * mount — testifies; when this module cannot reach the package at all, the
228
+ * `@deepseek-ai/dsh` host manifest supplies a second anchor for host-internal
229
+ * packages that are never plugin peers.
230
+ *
231
+ * {@link RUNNER_ANCHORED_PACKAGES} are a third policy: executed, but resolved
232
+ * from the upstream-runner anchor rather than this module, so only a hit
233
+ * sibling to the installed `dsh-sandbox-local` testifies.
234
+ *
235
+ * A package that cannot be resolved in either place is reported absent rather
236
+ * than throwing, because "not installed" is a legitimate answer for every
237
+ * optional package.
104
238
  * @param packageName - the package to inspect.
105
239
  * @returns the declared version, or `undefined` when the package or its
106
240
  * manifest cannot be resolved.
107
241
  */
108
242
  function readInstalledVersion(packageName) {
109
243
  const require = createRequire(import.meta.url);
110
- try {
111
- const manifest = require(`${packageName}/package.json`);
112
- return typeof manifest.version === "string" ? manifest.version : void 0;
113
- } catch {
114
- if (!HOST_RESOLVED_PACKAGES.includes(packageName)) return void 0;
115
- try {
116
- const manifest = createRequire(require.resolve("@deepseek-ai/dsh/package.json"))(`${packageName}/package.json`);
117
- return typeof manifest.version === "string" ? manifest.version : void 0;
118
- } catch {
119
- return;
120
- }
244
+ if (RUNNER_ANCHORED_PACKAGES.includes(packageName)) {
245
+ const anchor = resolvePackageJson(require, RUNNER_ANCHOR_PACKAGE);
246
+ const hit = anchor === void 0 ? void 0 : resolvePackageJson(createRequire(anchor), packageName);
247
+ return hit === void 0 ? void 0 : versionOfManifest(hit);
248
+ }
249
+ if (!HOST_SERVICE_PACKAGES.includes(packageName)) {
250
+ const hit = resolvePackageJson(require, packageName);
251
+ return hit === void 0 ? void 0 : versionOfManifest(hit);
121
252
  }
253
+ const root = pluginPackageRoot(require);
254
+ const selfHit = resolvePackageJson(require, packageName);
255
+ if (selfHit !== void 0 && !insidePackageRoot(selfHit, root)) return versionOfManifest(selfHit);
256
+ const host = hostAnchorRequire(require);
257
+ const hostHit = host === void 0 ? void 0 : resolvePackageJson(host, packageName);
258
+ return hostHit === void 0 || insidePackageRoot(hostHit, root) ? void 0 : versionOfManifest(hostHit);
122
259
  }
123
260
  /**
124
261
  * Judge one installation against the contract.
@@ -223,7 +360,7 @@ function report(verdict, release, packages, lines) {
223
360
  * Resolution follows the optional-peer precedent (`agent-instructions.ts`):
224
361
  * the backend is not a peer of this plugin, so the plugin anchor is tried
225
362
  * first (flat layouts) and the `@deepseek-ai/dsh` host anchor second — the
226
- * same `HOST_RESOLVED_PACKAGES` pattern as `dsh-version.ts` — because the
363
+ * same host-anchor fallback pattern as `dsh-version.ts` — because the
227
364
  * backend rides inside the harness meta package's own dependency tree.
228
365
  *
229
366
  * @module @dsh-electron/dsh-plugin-multi-root-workspace/compat/pty-marker
@@ -256,7 +393,7 @@ var PtyMarkerContractError = class extends Error {
256
393
  * reach: this module first (flat layouts where the backend is a direct
257
394
  * install), then the `@deepseek-ai/dsh` host manifest (pnpm-style layouts
258
395
  * where it is the meta package's own dependency — the
259
- * `HOST_RESOLVED_PACKAGES` precedent). A resolution failure that is not a
396
+ * host-anchor fallback pattern in `dsh-version.ts`). A resolution failure that is not a
260
397
  * genuine NOT_FOUND propagates: a resolvable-but-broken specifier is not
261
398
  * evidence of absence.
262
399
  */
@@ -354,16 +491,26 @@ function assertAgentPtyMarker(io = defaultIo) {
354
491
  * `multi-root-compat`: the startup gate that turns the DSH compatibility
355
492
  * contract from documentation into a precondition.
356
493
  *
357
- * The four security-relevant rows of this bundle — `multi-root-fs`,
358
- * `multi-root-sandbox`, `multi-root-registry`, `multi-root-instructions` —
359
- * inject `multiRootCompat`. Cordis will not start a plugin whose injected
360
- * service is absent, so this row is what decides whether they run at all:
494
+ * Every widened-authority row of this bundle — `multi-root-providers`,
495
+ * `multi-root-registry`, `multi-root-instructions`, `multi-root-lsp`,
496
+ * `multi-root-workspace-files`, `multi-root-process-fence` — injects
497
+ * `multiRootCompat`. Cordis will not start a plugin whose injected service is
498
+ * absent, so this row is what decides whether they run at all:
361
499
  *
362
500
  * ```text
363
- * compat check passes → ctx.multiRootCompat exists → the providers start
501
+ * compat check passes → ctx.multiRootCompat exists → the gated rows start
364
502
  * compat check fails → the row throws → they never start
365
503
  * ```
366
504
  *
505
+ * The two PROVIDER rows (`multi-root-fs`, `multi-root-sandbox`) deliberately
506
+ * do NOT inject the gate: their widening is scope-driven, and the gated
507
+ * registry can never populate the scope on a refused installation — so they
508
+ * degenerate to the upstream providers. That is what keeps a refused gate a
509
+ * loud degradation (the plugin's features absent, the profile intact) instead
510
+ * of a dead profile: upstream `fs-sandbox`/`sandbox` are already disabled by
511
+ * the patch, so holding `ctx.fs`/`ctx.sandbox` back would take `ctx.shell`,
512
+ * `permissionPresets` and every confining tool down with them.
513
+ *
367
514
  * That is why the gate is a plain `apply` and not a `Service` subclass mounted
368
515
  * directly: the check has to be able to fail BEFORE the service exists. A
369
516
  * `Service` constructor has already run `super(ctx, name)` — and therefore
@@ -450,4 +597,4 @@ function apply(ctx) {
450
597
  ctx.plugin(MultiRootCompatService, report);
451
598
  }
452
599
  //#endregion
453
- export { inject as a, agentPtyMarkerVerdict as c, OPTIONAL_CORE_PACKAGES as d, REQUIRED_CORE_PACKAGES as f, readInstalledVersion as g, compatEnforcement as h, assertSupportedInstallation as i, assertAgentPtyMarker as l, classifyInstallation as m, MultiRootCompatService as n, AGENT_PTY_MARKER as o, SUPPORTED_DSH_RELEASES as p, apply as r, PtyMarkerContractError as s, DshCompatUnsupportedError as t, CORE_PACKAGES as u };
600
+ export { classifyInstallation as _, inject as a, agentPtyMarkerVerdict as c, HOST_SERVICE_PACKAGES as d, OPTIONAL_CORE_PACKAGES as f, SUPPORTED_DSH_RELEASES as g, RUNTIME_OPTIONAL_PACKAGES as h, assertSupportedInstallation as i, assertAgentPtyMarker as l, RUNNER_ANCHORED_PACKAGES as m, MultiRootCompatService as n, AGENT_PTY_MARKER as o, REQUIRED_CORE_PACKAGES as p, apply as r, PtyMarkerContractError as s, DshCompatUnsupportedError as t, CORE_PACKAGES as u, compatEnforcement as v, readInstalledVersion as y };
package/lib/compat.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as inject, i as assertSupportedInstallation, n as MultiRootCompatService, r as apply, t as DshCompatUnsupportedError } from "./compat-DF_yL1bB.js";
1
+ import { a as inject, i as assertSupportedInstallation, n as MultiRootCompatService, r as apply, t as DshCompatUnsupportedError } from "./compat-CagUNzRB.js";
2
2
  export { DshCompatUnsupportedError, MultiRootCompatService, apply, assertSupportedInstallation, inject };