@dsh-electron/dsh-plugin-multi-root-workspace 0.1.0 → 0.1.1

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 (39) hide show
  1. package/CHANGELOG.en.md +77 -0
  2. package/CHANGELOG.md +77 -0
  3. package/README.en.md +94 -20
  4. package/README.md +95 -19
  5. package/cordis.patch.yml +22 -1
  6. package/lib/client.js +102 -58
  7. package/lib/client.js.map +1 -1
  8. package/lib/{command-BSkrYOAf.js → command-DyOWujPo.js} +24 -61
  9. package/lib/command.js +2 -2
  10. package/lib/compat-nFYqN5wt.js +267 -0
  11. package/lib/compat.js +2 -0
  12. package/lib/{fs-D7UoCb7T.js → fs-D_Sw3o5z.js} +7 -3
  13. package/lib/fs.js +1 -1
  14. package/lib/index.js +8 -6
  15. package/lib/instructions-BRGoC82G.js +635 -0
  16. package/lib/instructions.js +2 -0
  17. package/lib/registry-DySMbjgW.js +793 -0
  18. package/lib/registry-lease-win32-dy8TQMD9.js +93 -0
  19. package/lib/registry.js +2 -466
  20. package/lib/{sandbox-BhuMTDgH.js → sandbox-D8Fw95FM.js} +51 -4
  21. package/lib/sandbox.js +1 -1
  22. package/lib/scope.js +1 -1
  23. package/lib/types/client/locales.d.ts +6 -0
  24. package/lib/types/command.d.ts +17 -0
  25. package/lib/types/compat/agent-instructions.d.ts +138 -0
  26. package/lib/types/compat/dsh-version.d.ts +119 -0
  27. package/lib/types/compat/llm-message.d.ts +34 -0
  28. package/lib/types/compat/sandbox-confine.d.ts +58 -0
  29. package/lib/types/compat.d.ts +85 -0
  30. package/lib/types/contract.d.ts +6 -8
  31. package/lib/types/index.d.ts +19 -5
  32. package/lib/types/instructions.d.ts +124 -0
  33. package/lib/types/registry-lease-win32.d.ts +29 -0
  34. package/lib/types/registry-lease.d.ts +62 -0
  35. package/lib/types/registry.d.ts +130 -19
  36. package/lib/types/roots.d.ts +1 -1
  37. package/lib/types/sandbox.d.ts +22 -2
  38. package/package.json +60 -13
  39. package/lib/{roots-DyT2jgEP.js → roots-DasWEBJk.js} +1 -1
@@ -21,6 +21,7 @@ export declare const zh: {
21
21
  'panel.additional': string;
22
22
  'panel.empty': string;
23
23
  'panel.emptyHint': string;
24
+ 'panel.noSession': string;
24
25
  'panel.loading': string;
25
26
  'panel.retry': string;
26
27
  'panel.close': string;
@@ -54,9 +55,11 @@ export declare const zh: {
54
55
  'error.not-found': string;
55
56
  'error.invalid-ref': string;
56
57
  'error.storage-unavailable': string;
58
+ 'error.registry-contended': string;
57
59
  'error.reveal-unavailable': string;
58
60
  'error.unavailable': string;
59
61
  'error.copy-failed': string;
62
+ 'error.session-not-found': string;
60
63
  'error.fallback': string;
61
64
  };
62
65
  /** The canonical key set. */
@@ -72,6 +75,7 @@ export declare const en: {
72
75
  'panel.additional': string;
73
76
  'panel.empty': string;
74
77
  'panel.emptyHint': string;
78
+ 'panel.noSession': string;
75
79
  'panel.loading': string;
76
80
  'panel.retry': string;
77
81
  'panel.close': string;
@@ -105,9 +109,11 @@ export declare const en: {
105
109
  'error.not-found': string;
106
110
  'error.invalid-ref': string;
107
111
  'error.storage-unavailable': string;
112
+ 'error.registry-contended': string;
108
113
  'error.reveal-unavailable': string;
109
114
  'error.unavailable': string;
110
115
  'error.copy-failed': string;
116
+ 'error.session-not-found': string;
111
117
  'error.fallback': string;
112
118
  };
113
119
  declare module '@deepseek-ai/dsh-client-ui-slots' {
@@ -62,5 +62,22 @@ export declare function renderRootsReport(primaryRoot: string, statuses: readonl
62
62
  * @param ctx - the host context carrying `commands`, `sandboxPolicy`, and the registry.
63
63
  */
64
64
  export declare function apply(ctx: Context): void;
65
+ /**
66
+ * Derive the primary root a panel request may act on.
67
+ *
68
+ * The browser names a live session; the host looks that session up and uses
69
+ * its immutable cwd. There is no client-supplied path and no deployment-default
70
+ * fallback: a panel call without a real host session has no workspace to
71
+ * manage. Property access would demand an injected `sessions` dependency this
72
+ * row does not name, so the lookup stays an explicit sibling `get` (the same
73
+ * pattern as `directoryPicker` / `subprocess`).
74
+ *
75
+ * @param ctx - the host context that may carry `sessions`.
76
+ * @param sessionId - the id the panel sent; already required by the wire schema.
77
+ * @returns the canonical cwd of that session.
78
+ * @throws {RootValidationError} `session-not-found` when the service is absent,
79
+ * the id does not name a live session, or that session has no cwd.
80
+ */
81
+ export declare function resolvePanelPrimaryRoot(ctx: Context, sessionId: string): string;
65
82
  /** The per-platform argv that reveals one directory in the OS file manager. */
66
83
  export declare function revealArgv(platform: NodeJS.Platform, path: string): readonly string[];
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Compatibility adapter for upstream's instruction discovery and rendering.
3
+ *
4
+ * `@deepseek-ai/dsh-agent-instructions` renamed its renderer between two
5
+ * releases this plugin supports, without changing its signature:
6
+ *
7
+ * ```text
8
+ * 0.1.5-rc.2 renderWorkspaceContext(files, options) RenderedWorkspaceContext
9
+ * 0.1.6-alpha.1 renderAgentInstructions(files, options) RenderedAgentInstructions
10
+ * ```
11
+ *
12
+ * `discoverBaselineInstructionFiles` kept both its name and its signature.
13
+ *
14
+ * All coupling to that package is concentrated here so the business layer
15
+ * (`src/instructions.ts`) only ever calls {@link renderInstructions} and
16
+ * {@link InstructionsApi.discover}. A future rename is one edit in this file,
17
+ * not a version check sprinkled through the provider.
18
+ *
19
+ * The export is picked by NAME rather than by comparing the release string:
20
+ * a structural probe also copes with upstream keeping both names for a release,
21
+ * or reshaping within one, which a version comparison does not.
22
+ *
23
+ * The package is loaded LAZILY, and its genuine ABSENCE is reported as absence
24
+ * rather than thrown. It is an optional peer: the default DSH bundle mounts it,
25
+ * but a minimal composition legitimately has neither it nor an agent, and in
26
+ * that case this plugin's instruction row must simply contribute nothing. A
27
+ * package that is present but cannot be evaluated is a different answer, and
28
+ * one this adapter reports instead of caching as absence.
29
+ *
30
+ * @module @dsh-electron/dsh-plugin-multi-root-workspace/compat/agent-instructions
31
+ */
32
+ import type { InstructionFile, LoadedInstructionFile, TruncatedInstruction } from '@deepseek-ai/dsh-agent-instructions';
33
+ export type { InstructionFile, LoadedInstructionFile, TruncatedInstruction };
34
+ /** Model-facing instruction text plus the byte-budget diagnostics. */
35
+ export interface RenderedInstructions {
36
+ /** The rendered text, within the requested budget. */
37
+ readonly text: string;
38
+ /** Files the budget excluded entirely. */
39
+ readonly omitted: readonly InstructionFile[];
40
+ /** Files the budget included only in part. */
41
+ readonly truncated: readonly TruncatedInstruction[];
42
+ }
43
+ /** The rendering budget, spelled the way both releases spell it. */
44
+ export interface RenderOptions {
45
+ /** UTF-8 byte cap for the whole rendered batch. */
46
+ readonly maxBytes: number;
47
+ /** Whether this batch supersedes a previously visible baseline. */
48
+ readonly replacePreviousBaseline?: boolean;
49
+ }
50
+ /** Discovery inputs this plugin uses; a subset of upstream's option bag. */
51
+ export interface DiscoverOptions {
52
+ /** The directory discovery starts from. */
53
+ readonly cwd: string;
54
+ /** The root the walk stops at; pinned to the additional root by the caller. */
55
+ readonly projectRoot?: string;
56
+ /** Harness home, so the user-global file is discovered from the right place. */
57
+ readonly dshHome?: string;
58
+ /** Directory entries that identify a project root. */
59
+ readonly projectRootMarkers?: readonly string[];
60
+ /** Ordered same-directory instruction candidates. */
61
+ readonly instructionFileCandidates?: readonly string[];
62
+ /** Ordered same-directory local-overlay candidates. */
63
+ readonly localInstructionFileCandidates?: readonly string[];
64
+ /** Cancellation for the probes discovery performs. */
65
+ readonly signal?: AbortSignal;
66
+ }
67
+ /** The upstream surface this plugin depends on, after adaptation. */
68
+ export interface InstructionsApi {
69
+ /** Discover instruction candidates, in model precedence order. */
70
+ discover: (options: DiscoverOptions) => Promise<readonly InstructionFile[]>;
71
+ /** Render loaded files within a byte budget. */
72
+ render: (files: readonly LoadedInstructionFile[], options: RenderOptions) => RenderedInstructions;
73
+ }
74
+ /**
75
+ * The shape this adapter probes for. Both renderer names are optional because
76
+ * exactly one of them exists on any given release.
77
+ */
78
+ interface UpstreamModule {
79
+ readonly discoverBaselineInstructionFiles?: (options: DiscoverOptions) => Promise<InstructionFile[]>;
80
+ readonly renderAgentInstructions?: (files: readonly LoadedInstructionFile[], options: RenderOptions) => RenderedInstructions;
81
+ readonly renderWorkspaceContext?: (files: readonly LoadedInstructionFile[], options: RenderOptions) => RenderedInstructions;
82
+ }
83
+ /** Raised when the package is present but carries neither renderer name. */
84
+ export declare class DshInstructionApiError extends Error {
85
+ constructor(message: string);
86
+ }
87
+ /**
88
+ * Pick the renderer this release exports.
89
+ * @param module - the loaded upstream module.
90
+ * @returns the renderer.
91
+ * @throws {DshInstructionApiError} when neither name is present.
92
+ */
93
+ export declare function selectRenderer(module: UpstreamModule): NonNullable<UpstreamModule['renderAgentInstructions']>;
94
+ /**
95
+ * Adapt one loaded upstream module to {@link InstructionsApi}.
96
+ * @param module - the loaded upstream module.
97
+ * @returns the adapted surface.
98
+ * @throws {DshInstructionApiError} when a function this plugin needs is absent.
99
+ */
100
+ export declare function adaptInstructionsModule(module: UpstreamModule): InstructionsApi;
101
+ /**
102
+ * Whether one package is installed at all, judged WITHOUT evaluating it.
103
+ *
104
+ * This is what separates "the optional peer is absent" from "the optional peer
105
+ * is installed but its evaluation fails" (a missing transitive dependency, a
106
+ * broken build). Only a genuine resolvability failure counts as absence: a
107
+ * resolution that fails for another reason (an `exports` map with no `require`
108
+ * condition, say) leaves the verdict to the `import()` below, which is the
109
+ * authority on whether the module can actually be loaded.
110
+ * @param specifier - the bare package specifier to probe.
111
+ * @returns whether the package resolves from this module.
112
+ */
113
+ export declare function isPackageInstalled(specifier: string): boolean;
114
+ /**
115
+ * Load and adapt the upstream instruction surface, once.
116
+ *
117
+ * @returns the adapted surface, or `undefined` when the package is not
118
+ * installed. A package that IS installed but exports neither renderer throws
119
+ * instead: that is a compatibility break to fix, not an optional seam. So
120
+ * does one that is installed but cannot be evaluated at all — a missing
121
+ * transitive dependency, a broken build — because reporting that as absence
122
+ * would drop every additional root's instructions for the rest of the
123
+ * process. See {@link isPackageInstalled} for how the two are told apart.
124
+ */
125
+ export declare function instructionsApi(): Promise<InstructionsApi | undefined>;
126
+ /**
127
+ * Render loaded instruction files within a byte budget, through whichever
128
+ * renderer the installed release exports.
129
+ *
130
+ * This is the only rendering entry point the business layer uses.
131
+ * @param files - loaded files, ordered broadest to most specific.
132
+ * @param options - the byte budget.
133
+ * @returns the rendered text and its budget diagnostics.
134
+ * @throws {DshInstructionApiError} when no usable renderer is installed.
135
+ */
136
+ export declare function renderInstructions(files: readonly LoadedInstructionFile[], options: RenderOptions): Promise<RenderedInstructions>;
137
+ /** Reset the cached resolution. Tests use it to probe both renderer names. */
138
+ export declare function resetInstructionsApi(): void;
@@ -0,0 +1,119 @@
1
+ /**
2
+ * The DSH compatibility contract: which upstream releases this plugin is
3
+ * actually verified against, and how an installation is judged against that
4
+ * list.
5
+ *
6
+ * This module is the ONE place the support matrix is spelled out. It is
7
+ * deliberately pure — no cordis, no logging, no process exit — so that the
8
+ * runtime gate (`src/compat.ts`), the static gate (`scripts/check-dsh-compat.mjs`)
9
+ * and the tests all judge an installation by the same code instead of three
10
+ * drifting copies of the same list.
11
+ *
12
+ * Why a contract exists at all: this plugin replaces `ctx.fs` and `ctx.sandbox`
13
+ * with subclasses of the upstream providers, and `src/dialects.ts` recognizes
14
+ * the kernel-sandbox profile by OBSERVING the argv shape `super.confine`
15
+ * produced (ADR-0003). Both couplings are to pre-stable upstream internals that
16
+ * carry no semver promise. An npm range like `>=0.1.2-alpha.4 <0.2.0` therefore
17
+ * promises far more than the code was ever verified against; the allowlist
18
+ * below promises exactly what CI has run.
19
+ *
20
+ * @module @dsh-electron/dsh-plugin-multi-root-workspace/compat/dsh-version
21
+ */
22
+ /**
23
+ * The releases this plugin is verified against, as EXACT version strings.
24
+ *
25
+ * Not a semver range, on purpose: a range is a prediction about releases that
26
+ * do not exist yet, and this plugin's coupling to upstream argv shapes makes
27
+ * that prediction unsafe. Adding an entry is always a human act performed after
28
+ * the upgrade smoke passed on that exact version — a green CI run on an
29
+ * unlisted release is evidence, not authorization (ADR-0009).
30
+ */
31
+ export declare const SUPPORTED_DSH_RELEASES: readonly ["0.1.5-rc.2", "0.1.6-alpha.1"];
32
+ /** One release this plugin claims to support. */
33
+ export type SupportedDshRelease = typeof SUPPORTED_DSH_RELEASES[number];
34
+ /**
35
+ * Packages whose absence means this plugin cannot function at all: every one of
36
+ * them is either subclassed, imported for a value, or the owner of a service
37
+ * key the providers inject.
38
+ */
39
+ export declare const REQUIRED_CORE_PACKAGES: readonly ["@deepseek-ai/dsh-fs", "@deepseek-ai/dsh-fs-local", "@deepseek-ai/dsh-sandbox", "@deepseek-ai/dsh-sandbox-local", "@deepseek-ai/dsh-sandbox-policy", "@deepseek-ai/dsh-storage-domain"];
40
+ /**
41
+ * Packages a full composition provides and a minimal one legitimately omits.
42
+ * Absence is fine; a version that DISAGREES with the required packages is not —
43
+ * that is the mixed-installation failure this contract exists to catch.
44
+ */
45
+ export declare const OPTIONAL_CORE_PACKAGES: readonly ["@deepseek-ai/dsh-agent", "@deepseek-ai/dsh-agent-instructions", "@deepseek-ai/dsh-client-connection", "@deepseek-ai/dsh-commands", "@deepseek-ai/dsh-llm", "@deepseek-ai/dsh-session", "@deepseek-ai/dsh-subprocess", "@deepseek-ai/dsh-system-prompt"];
46
+ /** Every package the contract inspects, required first. */
47
+ export declare const CORE_PACKAGES: readonly ["@deepseek-ai/dsh-fs", "@deepseek-ai/dsh-fs-local", "@deepseek-ai/dsh-sandbox", "@deepseek-ai/dsh-sandbox-local", "@deepseek-ai/dsh-sandbox-policy", "@deepseek-ai/dsh-storage-domain", "@deepseek-ai/dsh-agent", "@deepseek-ai/dsh-agent-instructions", "@deepseek-ai/dsh-client-connection", "@deepseek-ai/dsh-commands", "@deepseek-ai/dsh-llm", "@deepseek-ai/dsh-session", "@deepseek-ai/dsh-subprocess", "@deepseek-ai/dsh-system-prompt"];
48
+ /** How this plugin reacts to an installation it does not support. */
49
+ export type CompatEnforcement = 'enforce' | 'warn';
50
+ /** The environment variable that relaxes the gate. */
51
+ export declare const COMPAT_ENFORCEMENT_ENV = "DSH_MULTI_ROOT_COMPAT";
52
+ /** One inspected package and the version actually resolved for it. */
53
+ export interface InspectedPackage {
54
+ /** The package specifier. */
55
+ readonly name: string;
56
+ /** The resolved `version`, or `undefined` when the package is not installed. */
57
+ readonly version: string | undefined;
58
+ /** Whether absence alone is a failure. */
59
+ readonly required: boolean;
60
+ }
61
+ /**
62
+ * Why an installation was accepted or rejected.
63
+ *
64
+ * - `supported` — one release, and it is on the allowlist.
65
+ * - `incomplete` — a required package could not be resolved.
66
+ * - `mixed` — the installed core packages do not agree on one release.
67
+ * - `unsupported` — one release, but not on the allowlist.
68
+ */
69
+ export type CompatVerdict = 'supported' | 'incomplete' | 'mixed' | 'unsupported';
70
+ /** The judgement passed on one installation. */
71
+ export interface CompatReport {
72
+ /** The verdict. */
73
+ readonly verdict: CompatVerdict;
74
+ /** The single release every inspected package agreed on; absent unless they did. */
75
+ readonly release: string | undefined;
76
+ /** Every inspected package, in {@link CORE_PACKAGES} order. */
77
+ readonly packages: readonly InspectedPackage[];
78
+ /** Operator-facing explanation, ready to log or print verbatim. */
79
+ readonly message: string;
80
+ }
81
+ /** Reads the installed version of one package, or `undefined` when absent. */
82
+ export type VersionReader = (packageName: string) => string | undefined;
83
+ /**
84
+ * Read one package's declared version through Node resolution.
85
+ *
86
+ * Resolution runs from THIS module, which is what makes the answer meaningful:
87
+ * the upstream packages are peer dependencies, so they resolve to the single
88
+ * copy the host provides — the same copy the providers will subclass at
89
+ * runtime. A package that cannot be resolved is reported absent rather than
90
+ * throwing, because "not installed" is a legitimate answer for every optional
91
+ * package.
92
+ * @param packageName - the package to inspect.
93
+ * @returns the declared version, or `undefined` when the package or its
94
+ * manifest cannot be resolved.
95
+ */
96
+ export declare function readInstalledVersion(packageName: string): string | undefined;
97
+ /**
98
+ * Judge one installation against the contract.
99
+ *
100
+ * The three checks run in this order, and the first failure wins, because each
101
+ * later check would report nonsense about an installation that failed an
102
+ * earlier one: a missing required package has no version to compare, and a
103
+ * mixed installation has no single version to look up in the allowlist.
104
+ * @param readVersion - version reader; defaults to Node resolution. Tests and
105
+ * the static checker pass their own.
106
+ * @returns the report; never throws.
107
+ */
108
+ export declare function classifyInstallation(readVersion?: VersionReader): CompatReport;
109
+ /**
110
+ * Read the enforcement mode.
111
+ *
112
+ * `warn` exists for exactly one caller: the upgrade smoke, which must run the
113
+ * whole behavior matrix against a release that is deliberately NOT on the
114
+ * allowlist yet — that run is how a release earns its place on the list. It is
115
+ * not a production escape hatch, and the warning says so.
116
+ * @param env - the environment to read; defaults to this process's.
117
+ * @returns `warn` only when explicitly requested, `enforce` otherwise.
118
+ */
119
+ export declare function compatEnforcement(env?: Record<string, string | undefined>): CompatEnforcement;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Compatibility adapter for the one LLM helper this bundle needs.
3
+ *
4
+ * `@deepseek-ai/dsh-llm` is an OPTIONAL peer (see `peerDependenciesMeta`): a
5
+ * minimal composition legitimately has neither it nor an agent, and the
6
+ * instruction row then contributes nothing. That makes the load MOMENT part of
7
+ * the contract — this bundle's business layer is re-exported by the barrel
8
+ * (`src/index.ts`), which is the module the carrier loader row mounts, so a
9
+ * static value import of the package would make that row fail to load in a
10
+ * composition that was never going to build a message at all.
11
+ *
12
+ * `createUserMessage` is therefore reached through this module, at the moment a
13
+ * message actually has to be built. Nothing else in the bundle imports the
14
+ * package for a value.
15
+ *
16
+ * @module @dsh-electron/dsh-plugin-multi-root-workspace/compat/llm-message
17
+ */
18
+ import type { UserMessage } from '@deepseek-ai/dsh-llm';
19
+ /** One plugin-attributed instruction message to build. */
20
+ export interface InstructionMessageInput {
21
+ /** The message body. */
22
+ readonly text: string;
23
+ /** The plugin credited with the message. */
24
+ readonly plugin: string;
25
+ }
26
+ /**
27
+ * Build one `{ kind: 'plugin', form: 'instructions' }` user-role message.
28
+ * @param input - the body and the plugin attribution.
29
+ * @returns the frozen message upstream's helper produced.
30
+ * @throws {Error} when the optional peer is absent: a step that has instruction
31
+ * text to deliver cannot be served without it, and staying silent would drop
32
+ * user-visible context instead of reporting a misconfigured composition.
33
+ */
34
+ export declare function createInstructionMessage(input: InstructionMessageInput): Promise<UserMessage>;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Compatibility adapter for the one upstream signature that changed shape
3
+ * between two releases this plugin supports.
4
+ *
5
+ * ```text
6
+ * 0.1.5-rc.2 confine(argv, policy): ConfinedArgv
7
+ * 0.1.6-alpha.1 confine(argv, policy, signal?): Promise<ConfinedArgv>
8
+ * ```
9
+ *
10
+ * The widening itself (`src/dialects.ts`) is pure string work on the argv the
11
+ * upstream provider produced, so nothing about it needs to be async. The only
12
+ * problem is the SHAPE of the call: an override must accept what the installed
13
+ * base class declares and return what its caller expects. This module is where
14
+ * that is solved, once:
15
+ *
16
+ * - the return type is expressed as {@link UpstreamConfined}, derived from the
17
+ * installed base class, so it resolves to `ConfinedArgv` on one release and
18
+ * `Promise<ConfinedArgv>` on the other without two hand-written signatures;
19
+ * - {@link widenConfined} preserves the shape it was handed — a synchronous
20
+ * base result stays synchronous, a promise stays a promise. It never wraps a
21
+ * sync result in a promise, because on `0.1.5-rc.2` that would change
22
+ * `ctx.sandbox.confine()` from a value into a thenable for every caller in
23
+ * the composition, including the bash executor and the PTY backend.
24
+ *
25
+ * The single unavoidable cast lives here too, and nowhere else: `super.confine`
26
+ * has a different arity per release, so calling it uniformly needs one
27
+ * assertion to {@link ConfineCall}.
28
+ *
29
+ * @module @dsh-electron/dsh-plugin-multi-root-workspace/compat/sandbox-confine
30
+ */
31
+ import type { ConfinedArgv, SandboxPolicy } from '@deepseek-ai/dsh-sandbox';
32
+ import type { LocalSandboxProvider } from '@deepseek-ai/dsh-sandbox-local';
33
+ /**
34
+ * Whatever the INSTALLED upstream provider's `confine` returns. Resolves to
35
+ * `ConfinedArgv` on a release with a synchronous `confine`, and to
36
+ * `Promise<ConfinedArgv>` on a release with an asynchronous one.
37
+ */
38
+ export type UpstreamConfined = ReturnType<LocalSandboxProvider['confine']>;
39
+ /**
40
+ * A call into the upstream `confine`, tolerant of both arities. The trailing
41
+ * `signal` is ignored by a release that does not declare it, which is safe:
42
+ * extra arguments to a JavaScript function are discarded, and a release without
43
+ * cancellation support simply never observes it.
44
+ */
45
+ export type ConfineCall = (argv: readonly string[], policy: SandboxPolicy, signal?: AbortSignal) => ConfinedArgv | Promise<ConfinedArgv>;
46
+ /**
47
+ * Call the upstream `confine` and widen its result, preserving sync/async shape.
48
+ *
49
+ * @param base - the upstream `confine`, already bound to its provider.
50
+ * @param widen - the pure transformation to apply to the upstream result. It
51
+ * runs exactly once per call, on whichever release; it may throw, and the
52
+ * throw surfaces synchronously or as a rejection to match the base shape.
53
+ * @param argv - the caller's argv, forwarded verbatim.
54
+ * @param policy - the per-call policy, forwarded verbatim.
55
+ * @param signal - forwarded when the installed release accepts it.
56
+ * @returns the widened result, in the shape the installed release uses.
57
+ */
58
+ export declare function widenConfined(base: ConfineCall, widen: (confined: ConfinedArgv) => ConfinedArgv, argv: readonly string[], policy: SandboxPolicy, signal?: AbortSignal): UpstreamConfined;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * `multi-root-compat`: the startup gate that turns the DSH compatibility
3
+ * contract from documentation into a precondition.
4
+ *
5
+ * The four security-relevant rows of this bundle — `multi-root-fs`,
6
+ * `multi-root-sandbox`, `multi-root-registry`, `multi-root-instructions` —
7
+ * inject `multiRootCompat`. Cordis will not start a plugin whose injected
8
+ * service is absent, so this row is what decides whether they run at all:
9
+ *
10
+ * ```text
11
+ * compat check passes → ctx.multiRootCompat exists → the providers start
12
+ * compat check fails → the row throws → they never start
13
+ * ```
14
+ *
15
+ * That is why the gate is a plain `apply` and not a `Service` subclass mounted
16
+ * directly: the check has to be able to fail BEFORE the service exists. A
17
+ * `Service` constructor has already run `super(ctx, name)` — and therefore
18
+ * already claimed the service key — by the time it could inspect anything.
19
+ *
20
+ * The verdict itself, and the allowlist it is judged against, live in
21
+ * `./compat/dsh-version.ts`. This module owns only the reaction to it.
22
+ *
23
+ * @module @dsh-electron/dsh-plugin-multi-root-workspace/compat
24
+ */
25
+ import { Context, Service } from '@deepseek-ai/cordis';
26
+ import { type CompatEnforcement, type CompatReport } from './compat/dsh-version.ts';
27
+ declare module '@deepseek-ai/cordis' {
28
+ interface Context {
29
+ multiRootCompat: MultiRootCompatService;
30
+ }
31
+ }
32
+ /**
33
+ * Raised when the installed DSH release is not one this plugin is verified
34
+ * against. It is thrown from `apply`, so it surfaces as a plugin load failure
35
+ * with the full diagnosis attached — never as a silently degraded sandbox.
36
+ */
37
+ export declare class DshCompatUnsupportedError extends Error {
38
+ /** The report that produced the refusal. */
39
+ readonly report: CompatReport;
40
+ constructor(report: CompatReport);
41
+ }
42
+ /**
43
+ * The `ctx.multiRootCompat` service: the resolved compatibility verdict, held
44
+ * where the gated providers can read it.
45
+ *
46
+ * It intentionally exposes no per-version capability flags. The adapters under
47
+ * `./compat/` detect the shape they need STRUCTURALLY — whether a return value
48
+ * is a thenable, which export name a module carries — because a structural
49
+ * probe also survives upstream reshaping something within one release, while a
50
+ * version comparison does not. The release string here is for the gate and for
51
+ * diagnostics.
52
+ */
53
+ export declare class MultiRootCompatService extends Service {
54
+ /** The verdict this composition started under. */
55
+ readonly report: CompatReport;
56
+ constructor(ctx: Context, report: CompatReport);
57
+ /** The single DSH release every inspected core package agreed on. */
58
+ get release(): string | undefined;
59
+ /** Whether the installation is on the allowlist. */
60
+ get supported(): boolean;
61
+ }
62
+ /** This row needs nothing: it is the thing everything else waits for. */
63
+ export declare const inject: string[];
64
+ /**
65
+ * Apply the gate policy to one report.
66
+ *
67
+ * Under `DSH_MULTI_ROOT_COMPAT=warn` exactly one verdict is relaxed: an
68
+ * `unsupported` release that every inspected package agrees on. That is the
69
+ * only shape the upgrade smoke probes — a coherent tree on a release the
70
+ * allowlist does not name yet, which is how a release earns its place there.
71
+ * `mixed` and `incomplete` describe an installation the contract cannot judge
72
+ * at all, so they are refused in either mode; production deployments leave the
73
+ * variable unset and get the refusal for everything.
74
+ * @param report - the verdict to act on.
75
+ * @param enforcement - how to react to a non-`supported` verdict.
76
+ * @returns the warning to log, or `undefined` when there is nothing to say.
77
+ * @throws {DshCompatUnsupportedError} when refusing to run.
78
+ */
79
+ export declare function assertSupportedInstallation(report: CompatReport, enforcement: CompatEnforcement): string | undefined;
80
+ /**
81
+ * Judge the installation, then either refuse to provide the service or provide
82
+ * it.
83
+ * @param ctx - the row's context.
84
+ */
85
+ export declare function apply(ctx: Context): void;
@@ -94,16 +94,14 @@ export interface PanelResponseMap {
94
94
  readonly reveal: RevealedView;
95
95
  }
96
96
  /**
97
- * The fields a request may carry. `primaryRoot` and `sessionId` are shared by
98
- * every endpoint (a client may name the workspace root directly or let the host
99
- * resolve it from the session); the remaining fields are used per endpoint and
100
- * the host asserts which ones that endpoint requires.
97
+ * The fields a request may carry. `sessionId` is required on every endpoint:
98
+ * the host derives the primary root from that session's immutable cwd and
99
+ * never accepts a client-named path. The remaining fields are used per
100
+ * endpoint and the host asserts which ones that endpoint requires.
101
101
  */
102
102
  export interface PanelRequest {
103
- /** The workspace root to act on; the host validates that it is an existing directory. */
104
- readonly primaryRoot?: string;
105
- /** The session whose workspace root should be used when `primaryRoot` is absent. */
106
- readonly sessionId?: string;
103
+ /** The live host session whose cwd is the primary root this call acts on. */
104
+ readonly sessionId: string;
107
105
  /** Exact target row from the most recent list snapshot. */
108
106
  readonly entry?: RootEntryView;
109
107
  /** Compatibility reference; accepted only when the id is unique. */
@@ -13,22 +13,36 @@
13
13
  * whole public surface from a single specifier.
14
14
  *
15
15
  * The two facts worth remembering about this surface: a registration carries the
16
- * canonical directory it was GRANTED for (`recordedPath`), and the panel channel
17
- * has one declared response shape per endpoint (`PanelResponseMap`).
16
+ * canonical directory it was GRANTED for (`recordedPath`), the panel channel
17
+ * has one declared response shape per endpoint (`PanelResponseMap`), and the
18
+ * panel primary root is derived from a live host session — never named by the
19
+ * browser.
18
20
  *
19
21
  * @module @dsh-electron/dsh-plugin-multi-root-workspace
20
22
  */
23
+ export { DshCompatUnsupportedError, MultiRootCompatService, assertSupportedInstallation } from './compat.ts';
24
+ export { classifyInstallation, compatEnforcement, CORE_PACKAGES, OPTIONAL_CORE_PACKAGES, readInstalledVersion, REQUIRED_CORE_PACKAGES, SUPPORTED_DSH_RELEASES, } from './compat/dsh-version.ts';
25
+ export type { CompatEnforcement, CompatReport, CompatVerdict, InspectedPackage, SupportedDshRelease, VersionReader, } from './compat/dsh-version.ts';
26
+ export { instructionsApi, renderInstructions } from './compat/agent-instructions.ts';
27
+ export type { InstructionsApi, RenderedInstructions } from './compat/agent-instructions.ts';
28
+ export { createInstructionMessage } from './compat/llm-message.ts';
29
+ export type { InstructionMessageInput } from './compat/llm-message.ts';
30
+ export { widenConfined } from './compat/sandbox-confine.ts';
31
+ export type { ConfineCall, UpstreamConfined } from './compat/sandbox-confine.ts';
32
+ export { composeInstructionMessage, DEFAULT_MAX_BYTES, PLUGIN_SOURCE } from './instructions.ts';
33
+ export type { Config as InstructionsConfig } from './instructions.ts';
21
34
  export { MultiRootFileSystem } from './fs.ts';
22
35
  export type { Config as FileSystemConfig } from './fs.ts';
23
36
  export { MultiRootSandboxProvider } from './sandbox.ts';
24
37
  export type { Config as SandboxConfig } from './sandbox.ts';
25
38
  export { MultiRootScopeService, sanitizeAdditionalRoots, renderWorkspaceRootsContext } from './scope.ts';
26
39
  export type { AdditionalWorkspaceRoot, FilesystemScope } from './scope.ts';
27
- export { MultiRootRegistry, DOMAIN_NAME, MAX_ALIAS_LENGTH, multiRootDomainSpec } from './registry.ts';
28
- export type { AddRootInput, PersistedPrimaryRoot } from './registry.ts';
40
+ export { MultiRootRegistry, DOMAIN_NAME, MAX_ALIAS_LENGTH, REGISTRY_CONTENDED_MESSAGE, multiRootDomainSpec } from './registry.ts';
41
+ export type { AddRootInput, Config as RegistryConfig, PersistedPrimaryRoot, RegistryAuthorityState } from './registry.ts';
42
+ export { RegistryAuthorityLease, RegistryLeaseContendedError } from './registry-lease.ts';
29
43
  export { availableRoots, canonicalRoot, classifyStoredRoots, expandRootInput, indexedStatuses, isCanonicallyUnder, additionalRootId, removeStatusAt, resolveRootRef, RootValidationError, validateRootCandidate, } from './roots.ts';
30
44
  export type { AdditionalRootId, RegisteredRoot, RootRef, RootState, RootStatus, RootValidationCode, } from './roots.ts';
31
- export { COMMAND_NAME, parseFoldersCommand, renderRootsReport, revealArgv } from './command.ts';
45
+ export { COMMAND_NAME, parseFoldersCommand, renderRootsReport, resolvePanelPrimaryRoot, revealArgv } from './command.ts';
32
46
  export type { FoldersCommand } from './command.ts';
33
47
  export { PANEL_CHANNEL, PANEL_ENDPOINTS, parsePanelCall, parseRevealedView, parseRootsView } from './contract.ts';
34
48
  export type { PanelCall, PanelEndpoint, PanelFailure, PanelRequest, PanelResponseMap, RevealedView, RootView, RootsView, } from './contract.ts';