dsh-plugin-wsl-env 0.11.0 → 0.13.0

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 (45) hide show
  1. package/CHANGELOG.md +181 -0
  2. package/CONTRIBUTING.md +29 -7
  3. package/README.md +124 -138
  4. package/SECURITY.md +11 -1
  5. package/SUPPORT.md +29 -2
  6. package/cordis.patch.yml +17 -19
  7. package/docs/ARCHITECTURE.md +30 -29
  8. package/docs/CONFIGURATION.md +81 -21
  9. package/docs/LIMITATIONS.md +71 -6
  10. package/docs/MIGRATION.md +98 -0
  11. package/docs/PARITY.md +1 -1
  12. package/docs/PERFORMANCE.md +60 -0
  13. package/docs/QUICKSTART.md +125 -0
  14. package/docs/README.zh.md +117 -137
  15. package/docs/ROADMAP-1.0.md +780 -0
  16. package/docs/ROADMAP.md +4 -0
  17. package/docs/UPSTREAM-FILE-REFERENCE-SEAM.md +80 -0
  18. package/docs/UPSTREAM-LSP-SEAM.md +90 -0
  19. package/docs/UPSTREAM-SESSION-NOTICE.md +95 -0
  20. package/lib/agent-errors.d.ts +19 -0
  21. package/lib/agent-errors.js +22 -0
  22. package/lib/agent-exec.js +2 -2
  23. package/lib/agent.d.ts +68 -5
  24. package/lib/agent.js +129 -22
  25. package/lib/distro-route.d.ts +75 -0
  26. package/lib/distro-route.js +86 -0
  27. package/lib/fs-routing.d.ts +4 -1
  28. package/lib/index.d.ts +166 -37
  29. package/lib/index.js +353 -75
  30. package/lib/paths.d.ts +15 -0
  31. package/lib/paths.js +28 -0
  32. package/lib/preflight.d.ts +46 -3
  33. package/lib/preflight.js +83 -14
  34. package/lib/shell-env.d.ts +9 -3
  35. package/lib/shell-env.js +121 -43
  36. package/lib/subprocess.d.ts +12 -3
  37. package/lib/subprocess.js +21 -3
  38. package/package.json +23 -15
  39. package/scripts/diagnose.sh +9 -88
  40. package/scripts/doctor.mjs +548 -0
  41. package/scripts/profile-preset.mjs +254 -0
  42. package/lib/workspace-files-route.d.ts +0 -136
  43. package/lib/workspace-files-route.js +0 -225
  44. package/lib/workspace-files-wsl.d.ts +0 -257
  45. package/lib/workspace-files-wsl.js +0 -418
package/lib/index.d.ts CHANGED
@@ -128,8 +128,8 @@ export interface WslShellExecutorConfig {
128
128
  };
129
129
  /** Whether execution prefers the resident agent. */
130
130
  agent: boolean;
131
- /** Whether commands run in one persistent session scope (ADR 0001). */
132
- sessionSandbox: boolean;
131
+ /** Session runtime: `true` requires it, `false` disables it, `"auto"` uses it when usable. */
132
+ sessionSandbox: boolean | "auto";
133
133
  /** The session runtime's agent backend; `auto` when omitted. */
134
134
  agentBackend?: AgentBackend;
135
135
  }
@@ -161,6 +161,8 @@ interface ProcessFacts {
161
161
  workdir: string;
162
162
  /** Whether the profile masks the Windows drive. */
163
163
  windowsDrive: "masked" | "visible";
164
+ /** Which runtime the process used: the session scope or a per-command profile. */
165
+ sessionScope: "session" | "per-command";
164
166
  }
165
167
  /**
166
168
  * Bash executor that runs every command inside a WSL distro.
@@ -174,7 +176,10 @@ interface ProcessFacts {
174
176
  export declare class WslShellExecutor extends LocalBashExecutor {
175
177
  static inject: string[];
176
178
  static Config: z<Schemastery.ObjectS<NoInfer<{
177
- /** Distro name; empty means WSL's own default distro. */
179
+ /**
180
+ * Distro mode: empty/`"auto"` follows the request workdir, `"default"`
181
+ * forces WSL's default, and an explicit name pins every command.
182
+ */
178
183
  distro: z<string, string, "defined">;
179
184
  /** Path to `wsl.exe`; overridable for a non-standard install. */
180
185
  wslPath: z<string, string, "defined">;
@@ -247,9 +252,12 @@ export declare class WslShellExecutor extends LocalBashExecutor {
247
252
  * `Linger`; the preflight refuses with the remedy when one is missing, and
248
253
  * only a success is cached. Off by default; requires `agent: true`.
249
254
  */
250
- sessionSandbox: z<boolean, boolean, "defined">;
255
+ sessionSandbox: z<boolean | "auto", boolean | "auto", "defined">;
251
256
  }>>, Schemastery.ObjectT<NoInfer<{
252
- /** Distro name; empty means WSL's own default distro. */
257
+ /**
258
+ * Distro mode: empty/`"auto"` follows the request workdir, `"default"`
259
+ * forces WSL's default, and an explicit name pins every command.
260
+ */
253
261
  distro: z<string, string, "defined">;
254
262
  /** Path to `wsl.exe`; overridable for a non-standard install. */
255
263
  wslPath: z<string, string, "defined">;
@@ -322,14 +330,18 @@ export declare class WslShellExecutor extends LocalBashExecutor {
322
330
  * `Linger`; the preflight refuses with the remedy when one is missing, and
323
331
  * only a success is cached. Off by default; requires `agent: true`.
324
332
  */
325
- sessionSandbox: z<boolean, boolean, "defined">;
333
+ sessionSandbox: z<boolean | "auto", boolean | "auto", "defined">;
326
334
  }>>, "plain">;
327
335
  /** Cached default distro, resolved once from `wsl.exe -l -q`. */
328
336
  resolvedDistro: string;
329
337
  /** Cached distro-user login shell, resolved once. */
330
338
  resolvedShell: string;
339
+ /** Per-distro login shells, for a workspace that is not the default distro. */
340
+ resolvedShells: Map<string, string>;
331
341
  /** Cached distro-user home, the workdir when `cwd` is empty. */
332
342
  resolvedHome: string | undefined;
343
+ /** Per-distro homes, so two concurrent distros cannot share one. */
344
+ resolvedHomes: Map<string, string>;
333
345
  /** The default mode — the capability fact the tool layer reads. */
334
346
  mode: SandboxMode | undefined;
335
347
  /** The distro-side confinement, or nothing when the operator opted out. */
@@ -340,8 +352,13 @@ export declare class WslShellExecutor extends LocalBashExecutor {
340
352
  sessionRuntimeEntries: Map<string, SessionRuntimeEntry>;
341
353
  /** The preflight's host runner; tests substitute it. */
342
354
  sessionScopeProbe: (argv: string[]) => Promise<string>;
343
- /** The last preflight verdict; only a success is cached. */
344
- sessionRuntimePreflight: "unknown" | "ready";
355
+ /** Distros whose session-runtime preflight has succeeded. */
356
+ sessionRuntimePreflight: Set<string>;
357
+ /** Per-distro `auto` verdicts, re-probed when they were negative and stale. */
358
+ autoSessionScope: Map<string, {
359
+ usable: boolean;
360
+ checkedAt: number;
361
+ }>;
345
362
  /**
346
363
  * Per-process confinement facts retained until settlement. Overlapping calls
347
364
  * can carry different policies, so a shared latest-wrap value would classify
@@ -363,21 +380,41 @@ export declare class WslShellExecutor extends LocalBashExecutor {
363
380
  */
364
381
  get sandboxMode(): SandboxMode | undefined;
365
382
  /**
366
- * The distro every command runs in, resolving WSL's default on first use.
383
+ * The distro used when no path supplies evidence: the pinned name, WSL's
384
+ * default, or the default for the automatic modes.
367
385
  *
368
386
  * @returns the distro name.
369
387
  */
370
388
  distro(): Promise<string>;
371
389
  /**
372
- * The shell every command runs in: the configured one, else the login shell of
373
- * the distro user, resolved once and cached.
390
+ * The distro one command belongs to: an explicit pin wins; otherwise the
391
+ * request workdir, then the configured `cwd`, may name a distro; both
392
+ * absent falls back to WSL's default.
374
393
  *
375
- * @returns an absolute path to the shell inside the distro.
394
+ * @param workdir - the request's workdir, in any coordinate system.
395
+ * @param cwd - the configured default workdir, when the caller has it.
396
+ * @returns the distro name.
397
+ */
398
+ distroFor(workdir?: string, cwd?: string): Promise<string>;
399
+ /**
400
+ * The shell name for the default/pinned distro, as the shell tool presents
401
+ * it.
402
+ *
403
+ * @returns the basename of the shell, or `bash` when it cannot be named.
376
404
  */
377
405
  shellName(): Promise<string>;
378
406
  /**
379
- * The shell every command runs in, resolving the distro login shell on
380
- * first use.
407
+ * The shell for one distro: the configured shell, else that distro's login
408
+ * shell. The default/pinned distro keeps the legacy {@link resolvedShell}
409
+ * cache; every other distro gets an entry in {@link resolvedShells}.
410
+ *
411
+ * @param distro - the distro name.
412
+ * @returns an absolute path to the shell inside the distro.
413
+ */
414
+ shellFor(distro: string): Promise<string>;
415
+ /**
416
+ * The shell for the default/pinned distro: the configured one, else the
417
+ * login shell, resolved once and cached.
381
418
  *
382
419
  * @returns an absolute path to the shell inside the distro.
383
420
  */
@@ -422,6 +459,15 @@ export declare class WslShellExecutor extends LocalBashExecutor {
422
459
  * @throws {Error} with the remedy when it cannot.
423
460
  */
424
461
  ensureSessionRuntimeUsable(distro: string): Promise<void>;
462
+ /**
463
+ * Whether this executor should use the session runtime for one distro, with
464
+ * the `auto` downgrade and its bounded re-probe. `true` keeps the refusing
465
+ * behavior; `false` stays off.
466
+ *
467
+ * @param distro - the resolved distro.
468
+ * @returns whether to acquire the per-session scope.
469
+ */
470
+ wantsSessionRuntime(distro: string): Promise<boolean>;
425
471
  /**
426
472
  * Release every runtime this executor holds, stopping each scope. Called
427
473
  * from the session's dispose hook; public so tests and shutdown paths can
@@ -516,7 +562,7 @@ export declare class WslShellExecutor extends LocalBashExecutor {
516
562
  */
517
563
  static decorateResult<H extends {
518
564
  result(): Promise<unknown>;
519
- }>(ex: H, map: (result: Awaited<ReturnType<H["result"]>>) => Awaited<ReturnType<H["result"]>>, mapError: (error: unknown) => Awaited<ReturnType<H["result"]>>): H;
565
+ }>(ex: H, map: (result: Awaited<ReturnType<H["result"]>>) => Awaited<ReturnType<H["result"]>>, mapError: (error: unknown) => Awaited<ReturnType<H["result"]>> | Promise<Awaited<ReturnType<H["result"]>>>): H;
520
566
  /**
521
567
  * Stamp the per-process sandbox facts before `done` settles, so a background
522
568
  * process reports the same mode, denial and enforcement a foreground run
@@ -669,8 +715,8 @@ export interface WslFileSystemConfig {
669
715
  diffBasisMaxBytes?: number;
670
716
  /** Whether mutations are fenced by the policy. */
671
717
  sandbox: boolean;
672
- /** Whether confined mutations run in the session runtime (ADR 0001). */
673
- sessionSandbox: boolean;
718
+ /** Session runtime: `true` requires it, `false` disables it, `"auto"` uses it when usable. */
719
+ sessionSandbox: boolean | "auto";
674
720
  /** Whether confined profiles mask `/mnt`. */
675
721
  maskWindowsDrive: boolean;
676
722
  /** Whether paths leaving the distro are refused. */
@@ -687,7 +733,10 @@ export interface WslFileSystemConfig {
687
733
  export declare class WslFileSystem extends LocalFileSystem {
688
734
  static inject: string[];
689
735
  static Config: z<Schemastery.ObjectS<NoInfer<{
690
- /** Distro name; empty resolves WSL's default on first use. */
736
+ /**
737
+ * Distro mode: empty/`"auto"` follows the target path, `"default"` forces
738
+ * WSL's default, and an explicit name pins the filesystem.
739
+ */
691
740
  distro: z<string, string, "defined">;
692
741
  /**
693
742
  * Default workdir for relative input; accepts a Linux path, a UNC path, or
@@ -717,7 +766,7 @@ export declare class WslFileSystem extends LocalFileSystem {
717
766
  * `sessionSandbox`; the preflight refuses with the remedy. Off for direct
718
767
  * constructions; on in the shipped preset.
719
768
  */
720
- sessionSandbox: z<boolean, boolean, "defined">;
769
+ sessionSandbox: z<boolean | "auto", boolean | "auto", "defined">;
721
770
  /**
722
771
  * Shadow `/mnt` with an empty tmpfs in every confined profile, hiding the
723
772
  * Windows drive from confined commands — same meaning and same honest
@@ -759,7 +808,10 @@ export declare class WslFileSystem extends LocalFileSystem {
759
808
  */
760
809
  substrate: z<"agent" | "share", "agent" | "share", "defined">;
761
810
  }>>, Schemastery.ObjectT<NoInfer<{
762
- /** Distro name; empty resolves WSL's default on first use. */
811
+ /**
812
+ * Distro mode: empty/`"auto"` follows the target path, `"default"` forces
813
+ * WSL's default, and an explicit name pins the filesystem.
814
+ */
763
815
  distro: z<string, string, "defined">;
764
816
  /**
765
817
  * Default workdir for relative input; accepts a Linux path, a UNC path, or
@@ -789,7 +841,7 @@ export declare class WslFileSystem extends LocalFileSystem {
789
841
  * `sessionSandbox`; the preflight refuses with the remedy. Off for direct
790
842
  * constructions; on in the shipped preset.
791
843
  */
792
- sessionSandbox: z<boolean, boolean, "defined">;
844
+ sessionSandbox: z<boolean | "auto", boolean | "auto", "defined">;
793
845
  /**
794
846
  * Shadow `/mnt` with an empty tmpfs in every confined profile, hiding the
795
847
  * Windows drive from confined commands — same meaning and same honest
@@ -835,22 +887,33 @@ export declare class WslFileSystem extends LocalFileSystem {
835
887
  resolvedDistro: string;
836
888
  /** Cached distro-user home, the base for relative input when `cwd` is empty. */
837
889
  resolvedHome: string | undefined;
890
+ /** Per-distro homes, so two concurrent distros cannot share one. */
891
+ resolvedHomes: Map<string, string>;
838
892
  /** The deployment default mode — the capability fact the tool layer reads. */
839
893
  defaultMode: SandboxMode;
840
- /** The lazy agent-backed substrate, built on first use (see `agentSubstrate`). */
894
+ /** The lazy default-distro substrate, kept for the legacy accessor and tests. */
841
895
  agentSubstrateInstance: AgentSubstrate | undefined;
896
+ /** The per-distro agent-backed substrates. */
897
+ agentSubstrates: Map<string, AgentSubstrate>;
842
898
  /** The distro-side bwrap usability probe, shared with the confined agents. */
843
899
  sandbox: InstanceType<typeof WslSandbox>;
844
- /** The resident agent serving publication (see `publicationAgent`). */
900
+ /** The resident agent serving publication for the default/pinned distro. */
845
901
  agentInstance: WslAgent | undefined;
902
+ /** The per-distro publication agents. */
903
+ agentInstances: Map<string, WslAgent>;
846
904
  /** The session-runtime registry; tests substitute it. */
847
905
  sessionRuntimes: SessionRuntimes;
848
906
  /** The runtimes this provider holds, by registry key. */
849
907
  sessionRuntimeEntries: Map<string, SessionRuntimeEntry>;
850
908
  /** The preflight's host runner; tests substitute it. */
851
909
  sessionScopeProbe: (argv: string[]) => Promise<string>;
852
- /** The last preflight verdict; only a success is cached. */
853
- sessionRuntimePreflight: "unknown" | "ready";
910
+ /** Distros whose session-runtime preflight has succeeded. */
911
+ sessionRuntimePreflight: Set<string>;
912
+ /** Per-distro `auto` verdicts, re-probed when they were negative and stale. */
913
+ autoSessionScope: Map<string, {
914
+ usable: boolean;
915
+ checkedAt: number;
916
+ }>;
854
917
  /**
855
918
  * @param ctx - the composition context.
856
919
  * @param config - the validated plugin config.
@@ -866,20 +929,45 @@ export declare class WslFileSystem extends LocalFileSystem {
866
929
  */
867
930
  get sandboxMode(): SandboxMode | undefined;
868
931
  /**
869
- * The resident agent serving this filesystem's in-distro side work, created
870
- * on first use and kept for the provider's lifetime (its own idle timer
871
- * retires the process; a later call starts a fresh one).
932
+ * The resident agent serving the default/pinned distro's in-distro side
933
+ * work, created on first use and kept for the provider's lifetime.
872
934
  *
873
- * @returns the agent for the pinned distro.
935
+ * @returns the agent for the default/pinned distro.
874
936
  */
875
937
  publicationAgent(): Promise<WslAgent>;
876
938
  /**
877
- * The agent-backed substrate, built on first use over the same resident
878
- * agent every other distro side work shares.
939
+ * The resident agent for one distro. The default/pinned distro keeps the
940
+ * legacy {@link publicationAgent} cache; every other distro gets its own
941
+ * entry, because the shared agent is a per-distro process singleton.
942
+ *
943
+ * @param distro - the distro name.
944
+ * @returns the agent for that distro.
945
+ */
946
+ publicationAgentFor(distro: string): Promise<WslAgent>;
947
+ /**
948
+ * The agent-backed substrate for the default/pinned distro, built on first
949
+ * use over the shared resident agent.
879
950
  *
880
951
  * @returns the substrate.
881
952
  */
882
953
  agentSubstrate(): Promise<AgentSubstrate>;
954
+ /**
955
+ * The agent-backed substrate for one distro. The default/pinned distro keeps
956
+ * the legacy {@link agentSubstrate} accessor (and its test override); every
957
+ * other distro gets a substrate that mints its own UNC identities and
958
+ * addresses its own resident.
959
+ *
960
+ * @param distro - the distro name.
961
+ * @returns the substrate for that distro.
962
+ */
963
+ agentSubstrateFor(distro: string): Promise<AgentSubstrate>;
964
+ /**
965
+ * The substrate for one resolved target, following the target's UNC identity.
966
+ *
967
+ * @param target - the resolved target.
968
+ * @returns the substrate for the target's distro.
969
+ */
970
+ substrateForTarget(target: FsTarget): Promise<AgentSubstrate>;
883
971
  /**
884
972
  * The persistent session runtime for one confined mutation, when
885
973
  * `sessionSandbox` is on. The key is the shell executor's key for the same
@@ -903,6 +991,15 @@ export declare class WslFileSystem extends LocalFileSystem {
903
991
  * @throws {Error} with the remedy when it cannot.
904
992
  */
905
993
  ensureSessionRuntimeUsable(distro: string): Promise<void>;
994
+ /**
995
+ * Whether this provider should use the session runtime for one distro, with
996
+ * the `auto` downgrade and its bounded re-probe. `true` keeps the refusing
997
+ * behavior; `false` stays off.
998
+ *
999
+ * @param distro - the resolved distro.
1000
+ * @returns whether to acquire the per-session scope.
1001
+ */
1002
+ wantsSessionRuntime(distro: string): Promise<boolean>;
906
1003
  /**
907
1004
  * Release every runtime this provider holds. Called from the preset's
908
1005
  * dispose effect; public so tests and shutdown paths can await it.
@@ -921,12 +1018,14 @@ export declare class WslFileSystem extends LocalFileSystem {
921
1018
  *
922
1019
  * @param policy - the resolved file-effect policy, or undefined when the
923
1020
  * caller pre-checked.
1021
+ * @param forDistro - the distro the mutation belongs to; derived from the
1022
+ * policy when the caller does not know it.
924
1023
  * @returns the agent to publish through.
925
1024
  */
926
1025
  mutationAgent(policy: {
927
1026
  mode: string;
928
1027
  workspaceRoot: string;
929
- } | undefined): Promise<WslAgent>;
1028
+ } | undefined, forDistro?: string): Promise<WslAgent>;
930
1029
  /**
931
1030
  * Map a substrate error onto the peer's `FsError`, so the tool layer reads
932
1031
  * the same refusal dialect on both substrates. An agent that is out becomes
@@ -946,11 +1045,41 @@ export declare class WslFileSystem extends LocalFileSystem {
946
1045
  */
947
1046
  linuxOf(target: FsTarget): string;
948
1047
  /**
949
- * The pinned distro, resolving WSL's default on first use.
1048
+ * The distro used when no path supplies evidence: the pinned name, WSL's
1049
+ * default, or the default for the automatic modes.
950
1050
  *
951
1051
  * @returns the distro name.
952
1052
  */
953
1053
  distro(): Promise<string>;
1054
+ /**
1055
+ * The distro one path belongs to: an explicit pin wins; otherwise the path
1056
+ * itself, then the cwd or configured cwd, may name a distro; both absent
1057
+ * falls back to WSL's default.
1058
+ *
1059
+ * @param path - the caller path, in any coordinate system.
1060
+ * @param cwd - the caller's optional cwd override.
1061
+ * @returns the distro name.
1062
+ */
1063
+ distroForPath(path: string, cwd?: string): Promise<string>;
1064
+ /**
1065
+ * The distro a resolved target belongs to. A UNC identity names it outright;
1066
+ * a host-coordinate target falls back to the configured/default distro.
1067
+ *
1068
+ * @param target - the resolved target.
1069
+ * @returns the distro name.
1070
+ */
1071
+ distroForTarget(target: FsTarget): Promise<string>;
1072
+ /**
1073
+ * The distro a mutation's policy belongs to: the workspace root is the
1074
+ * strongest evidence, then the configured cwd, then the default.
1075
+ *
1076
+ * @param policy - the resolved policy, when the caller has one.
1077
+ * @returns the distro name.
1078
+ */
1079
+ distroForPolicy(policy: {
1080
+ mode: string;
1081
+ workspaceRoot: string;
1082
+ } | undefined): Promise<string>;
954
1083
  /**
955
1084
  * Translate one caller path into the world path, enforcing the distro fence.
956
1085
  *
@@ -1064,10 +1193,10 @@ export declare class WslFileSystem extends LocalFileSystem {
1064
1193
  * plain callback over image attachments, and `packages/api/session-controller`
1065
1194
  * plus the UI's open/reveal path compare the return value against `undefined`
1066
1195
  * without awaiting. The mapping is therefore built from facts that are already
1067
- * known — the distro, once `distro()` has resolved it (the pinned config, or
1068
- * the default `wsl.exe` answered earlier: the first filesystem operation
1069
- * resolves it), and the configured `cwd`, or the resolved home for a relative
1070
- * path. A call that would have to ask `wsl.exe` — a cold default distro, or a
1196
+ * known — a UNC host path names its own distro outright; a non-UNC path uses
1197
+ * the pinned/config default once `distro()` has resolved it, and the
1198
+ * configured `cwd`, or the resolved home for a relative path. A call that
1199
+ * would have to ask `wsl.exe` — a cold default distro with no UNC, or a
1071
1200
  * relative path before the home is known — answers `undefined`, which every
1072
1201
  * consumer reads as "no mapping", the same answer the ssh provider gives for
1073
1202
  * a path it cannot map.