dsh-plugin-wsl-env 0.10.0 → 0.12.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.
package/lib/agent.d.ts CHANGED
@@ -59,6 +59,14 @@ export interface ExecResult {
59
59
  export interface PendingRequest {
60
60
  /** The request id, as the agent echoes it. */
61
61
  id: string;
62
+ /** Which metric bucket this request belongs to. */
63
+ kind: "fs" | "exec";
64
+ /** When the frame was written, for the queue measurement. */
65
+ sentAt: number;
66
+ /** When the agent ACKed it, for the execution measurement. */
67
+ ackedAt?: number;
68
+ /** Whether the metrics recorded this request's settle. */
69
+ measured: boolean;
62
70
  /** The frame lines (kept so a rebuild can re-send). */
63
71
  lines: string[];
64
72
  /** Settles a RES. */
@@ -80,6 +88,51 @@ export interface PendingRequest {
80
88
  /** The caller was already settled; a late answer only cleans up. */
81
89
  settled: boolean;
82
90
  }
91
+ /** Per-kind request metrics. */
92
+ export interface RequestMetrics {
93
+ /** Requests that settled (successful, failed or cancelled). */
94
+ requests: number;
95
+ /** Total queue wait (sent -> ACK), ms. */
96
+ queueMs: number;
97
+ /** Largest single queue wait, ms. */
98
+ queueMaxMs: number;
99
+ /** Total execution time (ACK -> settle), ms. */
100
+ execMs: number;
101
+ /** Largest single execution time, ms. */
102
+ execMaxMs: number;
103
+ /** Settled with an error (not cancellation). */
104
+ failed: number;
105
+ /** Cancelled by the caller. */
106
+ cancelled: number;
107
+ /** Rejected before tracking: queue full, already aborted, or send failed. */
108
+ rejected: number;
109
+ }
110
+ /** The agent's cumulative request metrics. */
111
+ export interface AgentMetrics {
112
+ /** FS frames. */
113
+ fs: RequestMetrics;
114
+ /** EXEC frames (commands, ping). */
115
+ exec: RequestMetrics;
116
+ /** Peak number of pending requests. */
117
+ inFlightMax: number;
118
+ }
119
+ /** The entry an async preparation may select before the first spawn. */
120
+ export interface WslAgentPrepared {
121
+ /** The agent entrypoint (the shell script or the native cache path). */
122
+ scriptPath: string;
123
+ /** The HELLO digest to verify. */
124
+ expectedDigest?: string;
125
+ /** Whether USR1 can reach the agent (false for the native KILL-frame path). */
126
+ signalable?: boolean;
127
+ /** The process factory for this entry. */
128
+ spawnTransport?: (options: {
129
+ wslPath: string;
130
+ distro: string;
131
+ scriptPath: string;
132
+ argvPrefix: string[];
133
+ leaseMs: number;
134
+ }) => ChildProcess;
135
+ }
83
136
  /**
84
137
  * The Windows environment the transport starts from, pinned.
85
138
  *
@@ -180,6 +233,10 @@ export declare class WslAgent {
180
233
  idleTimer: NodeJS.Timeout | null;
181
234
  /** The last failure, for `unavailableReason` and log messages. */
182
235
  lastError: string;
236
+ /** Cumulative request metrics; see {@link metrics}. */
237
+ requestMetrics: AgentMetrics;
238
+ /** The optional async preparation hook. */
239
+ prepare: (() => Promise<WslAgentPrepared | undefined>) | undefined;
183
240
  /**
184
241
  * @param config - the agent configuration.
185
242
  */
@@ -248,6 +305,8 @@ export declare class WslAgent {
248
305
  * refused instead of growing the request map without bound.
249
306
  */
250
307
  maxPending?: number;
308
+ /** Optional async preparation selecting the entry before the first spawn. */
309
+ prepare?: () => Promise<WslAgentPrepared | undefined>;
251
310
  });
252
311
  /**
253
312
  * Whether the agent is permanently out — the executor's fallback cue.
@@ -264,6 +323,18 @@ export declare class WslAgent {
264
323
  * @returns resolved once the state is `ready`.
265
324
  */
266
325
  start(): Promise<void>;
326
+ /**
327
+ * Apply an async preparation entry to this agent before the first spawn.
328
+ *
329
+ * @param entry - the selected entry.
330
+ */
331
+ private applyPrepared;
332
+ /**
333
+ * Spawn the process and run the handshake.
334
+ *
335
+ * @returns resolved once the state is `ready`.
336
+ */
337
+ private beginStart;
267
338
  /**
268
339
  * One stdout line from the agent. The first must be a matching HELLO.
269
340
  *
@@ -384,6 +455,38 @@ export declare class WslAgent {
384
455
  *
385
456
  * @param signal - the signal to deliver (`SIGUSR1` is the cancel trap).
386
457
  */
458
+ /**
459
+ * The cumulative request metrics: queue wait (sent -> ACK) and execution
460
+ * time (ACK -> settle) per request kind, plus the peak in-flight count.
461
+ *
462
+ * @returns a snapshot the caller can keep.
463
+ */
464
+ metrics(): AgentMetrics;
465
+ /** Reset the cumulative metrics (diagnostics/probes). */
466
+ resetMetrics(): void;
467
+ /**
468
+ * The metric bucket for a request kind.
469
+ * @param kind - fs or exec.
470
+ * @returns the bucket.
471
+ */
472
+ private kindMetrics;
473
+ /**
474
+ * Record a request that never entered the pending map.
475
+ * @param kind - the bucket.
476
+ */
477
+ private noteRejected;
478
+ /**
479
+ * Record the dequeue ACK: the request's queue wait.
480
+ * @param pending - the request.
481
+ */
482
+ private recordAck;
483
+ /**
484
+ * Record a settle: the requests count, the execution time (or the queue wait
485
+ * for a request that never ACKed), and the outcome.
486
+ * @param pending - the request.
487
+ * @param error - the failure, when any.
488
+ */
489
+ private recordSettle;
387
490
  private signalAgent;
388
491
  /**
389
492
  * Shut the agent down: SHUTDOWN first, then kill after a short grace. The
package/lib/agent.js CHANGED
@@ -40,6 +40,20 @@
40
40
  import { spawn } from "node:child_process";
41
41
  import { AgentUnavailableError, CwdError } from "./agent-errors.js";
42
42
  import { AGENT_NAME, MAX_FRAME_BYTES, PROTOCOL_VERSION, encodeExecFrame, encodeFsFrame, encodeKill, encodeSetEnv, parseAgentLine } from "./agent-protocol.js";
43
+ /**
44
+ * A zeroed per-kind metrics record.
45
+ * @returns the record.
46
+ */
47
+ function emptyRequestMetrics() {
48
+ return { requests: 0, queueMs: 0, queueMaxMs: 0, execMs: 0, execMaxMs: 0, failed: 0, cancelled: 0, rejected: 0 };
49
+ }
50
+ /**
51
+ * A zeroed metrics snapshot.
52
+ * @returns the snapshot.
53
+ */
54
+ function emptyAgentMetrics() {
55
+ return { fs: emptyRequestMetrics(), exec: emptyRequestMetrics(), inFlightMax: 0 };
56
+ }
43
57
  /** How long a rebuilt agent has to re-answer the handshake before giving up. */
44
58
  const HANDSHAKE_TIMEOUT_MS = 15000;
45
59
  /** Extra time the host watchdog waits past the request's own in-distro timeout. */
@@ -160,6 +174,8 @@ export class WslAgent {
160
174
  this.rebuildUsed = false;
161
175
  this.idleTimer = null;
162
176
  this.lastError = "";
177
+ this.requestMetrics = emptyAgentMetrics();
178
+ this.prepare = config.prepare;
163
179
  }
164
180
  /**
165
181
  * Whether the agent is permanently out — the executor's fallback cue.
@@ -187,9 +203,42 @@ export class WslAgent {
187
203
  return Promise.resolve();
188
204
  if (this.startPromise)
189
205
  return this.startPromise;
206
+ const prepared = this.prepare?.();
207
+ if (prepared === undefined) {
208
+ const started = this.beginStart();
209
+ this.startPromise = started;
210
+ return started;
211
+ }
212
+ const started = prepared.then((entry) => {
213
+ if (entry !== undefined)
214
+ this.applyPrepared(entry);
215
+ return this.beginStart();
216
+ });
217
+ this.startPromise = started;
218
+ return started;
219
+ }
220
+ /**
221
+ * Apply an async preparation entry to this agent before the first spawn.
222
+ *
223
+ * @param entry - the selected entry.
224
+ */
225
+ applyPrepared(entry) {
226
+ this.scriptPath = entry.scriptPath;
227
+ this.expectedDigest = entry.expectedDigest;
228
+ if (entry.signalable !== undefined)
229
+ this.signalable = entry.signalable;
230
+ if (entry.spawnTransport !== undefined)
231
+ this.configuredSpawn = entry.spawnTransport;
232
+ }
233
+ /**
234
+ * Spawn the process and run the handshake.
235
+ *
236
+ * @returns resolved once the state is `ready`.
237
+ */
238
+ beginStart() {
190
239
  this.state = "starting";
191
240
  this.agentPid = undefined;
192
- this.startPromise = new Promise((resolve, reject) => {
241
+ return new Promise((resolve, reject) => {
193
242
  // stdio is all-pipe (the spawn options above), so every standard stream
194
243
  // exists; the broad ChildProcess type models them as nullable.
195
244
  const child = (this.process = (this.configuredSpawn ?? spawnDefault)({
@@ -286,7 +335,6 @@ export class WslAgent {
286
335
  this.lastError = String(chunk).trim().slice(0, 2000) || this.lastError;
287
336
  });
288
337
  });
289
- return this.startPromise;
290
338
  }
291
339
  /**
292
340
  * One stdout line from the agent. The first must be a matching HELLO.
@@ -333,6 +381,7 @@ export class WslAgent {
333
381
  const pending = this.pending.get(message.id);
334
382
  if (pending) {
335
383
  pending.started = true;
384
+ this.recordAck(pending);
336
385
  if (pending.cancelled)
337
386
  this.signalAgent("SIGUSR1");
338
387
  pending.armWatchdog?.();
@@ -487,16 +536,26 @@ export class WslAgent {
487
536
  *
488
537
  * @param request - the request to register.
489
538
  */
490
- track({ id, lines, resolve, reject, timeoutMs, signal }) {
539
+ track({ id, kind = "exec", lines, resolve, reject, timeoutMs, signal }) {
491
540
  if (this.pending.size >= this.maxPending) {
541
+ this.noteRejected(kind);
492
542
  reject(new Error(`the resident agent has ${this.pending.size} requests in flight (max ${this.maxPending}); retry when the queue drains`));
493
543
  return;
494
544
  }
495
545
  const pending = {
496
546
  id,
547
+ kind,
548
+ sentAt: performance.now(),
549
+ measured: false,
497
550
  lines,
498
- resolve,
499
- reject,
551
+ resolve: (result) => {
552
+ this.recordSettle(pending, undefined);
553
+ resolve(result);
554
+ },
555
+ reject: (error) => {
556
+ this.recordSettle(pending, error);
557
+ reject(error);
558
+ },
500
559
  watchdog: null,
501
560
  signal: signal ?? null,
502
561
  onAbort: null,
@@ -525,9 +584,11 @@ export class WslAgent {
525
584
  : null,
526
585
  };
527
586
  this.pending.set(id, pending);
587
+ this.requestMetrics.inFlightMax = Math.max(this.requestMetrics.inFlightMax, this.pending.size);
528
588
  if (signal) {
529
589
  if (signal.aborted) {
530
590
  this.pending.delete(id);
591
+ this.noteRejected(kind);
531
592
  reject(signal.reason instanceof Error ? signal.reason : new Error("aborted"));
532
593
  return;
533
594
  }
@@ -557,6 +618,7 @@ export class WslAgent {
557
618
  if (!this.writeLines(lines)) {
558
619
  this.clearPending(pending);
559
620
  this.pending.delete(pending.id);
621
+ this.noteRejected(kind);
560
622
  reject(new AgentUnavailableError("the agent exited before the request could be sent; it never ran"));
561
623
  return;
562
624
  }
@@ -580,7 +642,7 @@ export class WslAgent {
580
642
  ...encodeExecFrame({ id, cwd, argv, timeoutMs, maxOutputBytes }),
581
643
  ];
582
644
  return new Promise((resolve, reject) => {
583
- this.track({ id, lines, resolve, reject, timeoutMs, signal });
645
+ this.track({ id, kind: "exec", lines, resolve, reject, timeoutMs, signal });
584
646
  });
585
647
  }
586
648
  /**
@@ -602,7 +664,7 @@ export class WslAgent {
602
664
  const id = `r${this.nextRequestId++}`;
603
665
  const lines = encodeFsFrame({ id, op, args, timeoutMs });
604
666
  return new Promise((resolve, reject) => {
605
- this.track({ id, lines, resolve, reject, timeoutMs, signal });
667
+ this.track({ id, kind: "fs", lines, resolve, reject, timeoutMs, signal });
606
668
  });
607
669
  }
608
670
  /**
@@ -620,7 +682,7 @@ export class WslAgent {
620
682
  return new Promise((resolve, reject) => {
621
683
  const id = `r${this.nextRequestId++}`;
622
684
  const lines = encodeExecFrame({ id, cwd: "/", argv: ["true"], timeoutMs: 0 });
623
- this.track({ id, lines, resolve: () => resolve(), reject, timeoutMs: HANDSHAKE_TIMEOUT_MS, signal });
685
+ this.track({ id, kind: "exec", lines, resolve: () => resolve(), reject, timeoutMs: HANDSHAKE_TIMEOUT_MS, signal });
624
686
  });
625
687
  }
626
688
  /**
@@ -637,6 +699,80 @@ export class WslAgent {
637
699
  *
638
700
  * @param signal - the signal to deliver (`SIGUSR1` is the cancel trap).
639
701
  */
702
+ /**
703
+ * The cumulative request metrics: queue wait (sent -> ACK) and execution
704
+ * time (ACK -> settle) per request kind, plus the peak in-flight count.
705
+ *
706
+ * @returns a snapshot the caller can keep.
707
+ */
708
+ metrics() {
709
+ return {
710
+ fs: { ...this.requestMetrics.fs },
711
+ exec: { ...this.requestMetrics.exec },
712
+ inFlightMax: this.requestMetrics.inFlightMax,
713
+ };
714
+ }
715
+ /** Reset the cumulative metrics (diagnostics/probes). */
716
+ resetMetrics() {
717
+ this.requestMetrics = emptyAgentMetrics();
718
+ }
719
+ /**
720
+ * The metric bucket for a request kind.
721
+ * @param kind - fs or exec.
722
+ * @returns the bucket.
723
+ */
724
+ kindMetrics(kind) {
725
+ return kind === "fs" ? this.requestMetrics.fs : this.requestMetrics.exec;
726
+ }
727
+ /**
728
+ * Record a request that never entered the pending map.
729
+ * @param kind - the bucket.
730
+ */
731
+ noteRejected(kind) {
732
+ this.kindMetrics(kind).rejected += 1;
733
+ }
734
+ /**
735
+ * Record the dequeue ACK: the request's queue wait.
736
+ * @param pending - the request.
737
+ */
738
+ recordAck(pending) {
739
+ if (pending.ackedAt !== undefined)
740
+ return;
741
+ const now = performance.now();
742
+ pending.ackedAt = now;
743
+ const stats = this.kindMetrics(pending.kind);
744
+ const wait = now - pending.sentAt;
745
+ stats.queueMs += wait;
746
+ stats.queueMaxMs = Math.max(stats.queueMaxMs, wait);
747
+ }
748
+ /**
749
+ * Record a settle: the requests count, the execution time (or the queue wait
750
+ * for a request that never ACKed), and the outcome.
751
+ * @param pending - the request.
752
+ * @param error - the failure, when any.
753
+ */
754
+ recordSettle(pending, error) {
755
+ if (pending.measured)
756
+ return;
757
+ pending.measured = true;
758
+ const now = performance.now();
759
+ const stats = this.kindMetrics(pending.kind);
760
+ stats.requests += 1;
761
+ if (pending.ackedAt !== undefined) {
762
+ const exec = now - pending.ackedAt;
763
+ stats.execMs += exec;
764
+ stats.execMaxMs = Math.max(stats.execMaxMs, exec);
765
+ }
766
+ else {
767
+ const wait = now - pending.sentAt;
768
+ stats.queueMs += wait;
769
+ stats.queueMaxMs = Math.max(stats.queueMaxMs, wait);
770
+ }
771
+ if (pending.cancelled)
772
+ stats.cancelled += 1;
773
+ else if (error !== undefined)
774
+ stats.failed += 1;
775
+ }
640
776
  signalAgent(signal) {
641
777
  const pid = this.agentPid;
642
778
  if (pid === undefined || !this.signalable)
package/lib/index.js CHANGED
@@ -246,7 +246,9 @@ export class WslShellExecutor extends LocalBashExecutor {
246
246
  this.resolvedShell = config.shell;
247
247
  this.resolvedHome = undefined;
248
248
  this.mode = config.sandbox ? ctx.sandboxPolicy.defaultMode : undefined;
249
- this.sandbox = config.sandbox ? new WslSandbox({ wslPath: config.wslPath }) : undefined;
249
+ this.sandbox = config.sandbox
250
+ ? new WslSandbox({ wslPath: config.wslPath, maskWindowsDrive: config.maskWindowsDrive })
251
+ : undefined;
250
252
  this.processFacts = new Map();
251
253
  this.sessionRuntimes = sessionRuntimes;
252
254
  this.sessionRuntimeEntries = new Map();
package/lib/paths.d.ts CHANGED
@@ -138,6 +138,21 @@ export declare function windowsToLinuxMount(winPath: string): string | undefined
138
138
  * @returns an absolute Linux path, or the input unchanged when it cannot be mapped.
139
139
  */
140
140
  export declare function toLinuxPath(value: string, options?: WorldPathOptions): string;
141
+ /**
142
+ * Join a requested path onto a workspace root in the distro's Linux
143
+ * coordinate system: UNC absolutes map to their Linux paths, POSIX absolutes
144
+ * win over the root, and relative paths join onto it.
145
+ *
146
+ * This is the path half of the shipped `workspace-files` service's `ctx.fs`
147
+ * calls: the routed backend receives the same path/`cwd` pairs a host
148
+ * workspace would, so the stock service can serve a distro workspace without a
149
+ * second implementation.
150
+ *
151
+ * @param linuxRoot - the workspace root in Linux form.
152
+ * @param path - the requested path, in any coordinate system.
153
+ * @returns the absolute Linux path.
154
+ */
155
+ export declare function linuxJoin(linuxRoot: string, path: string): string;
141
156
  /**
142
157
  * Resolve a caller-supplied path into the UNC coordinate system the host's fs
143
158
  * calls use. Linux paths from the model, UNC paths from the harness, and
package/lib/paths.js CHANGED
@@ -238,6 +238,34 @@ export function toLinuxPath(value, options) {
238
238
  const baseLinux = toLinuxPath(base, { distro: options?.distro });
239
239
  return `${baseLinux.replace(/\/+$/, "")}/${text.replace(/^\.\//, "")}`;
240
240
  }
241
+ /**
242
+ * Join a requested path onto a workspace root in the distro's Linux
243
+ * coordinate system: UNC absolutes map to their Linux paths, POSIX absolutes
244
+ * win over the root, and relative paths join onto it.
245
+ *
246
+ * This is the path half of the shipped `workspace-files` service's `ctx.fs`
247
+ * calls: the routed backend receives the same path/`cwd` pairs a host
248
+ * workspace would, so the stock service can serve a distro workspace without a
249
+ * second implementation.
250
+ *
251
+ * @param linuxRoot - the workspace root in Linux form.
252
+ * @param path - the requested path, in any coordinate system.
253
+ * @returns the absolute Linux path.
254
+ */
255
+ export function linuxJoin(linuxRoot, path) {
256
+ // The UNC check runs on the RAW spelling: the backslash-to-slash rewrite
257
+ // below would leave `//wsl.localhost/...`, which the identity regex cannot
258
+ // see.
259
+ const parsed = uncToPosix(path);
260
+ if (parsed !== undefined)
261
+ return parsed.linuxPath.length > 0 ? parsed.linuxPath : "/";
262
+ const text = String(path).replaceAll("\\", "/");
263
+ if (text.startsWith("/"))
264
+ return text.length > 1 ? text.replace(/\/+$/, "") : "/";
265
+ const base = linuxRoot === "/" ? "" : linuxRoot;
266
+ const joined = text.length === 0 ? base : `${base}/${text}`.replace(/\/\.(?=\/|$)/gu, "");
267
+ return joined.length === 0 ? "/" : joined;
268
+ }
241
269
  /**
242
270
  * Resolve a caller-supplied path into the UNC coordinate system the host's fs
243
271
  * calls use. Linux paths from the model, UNC paths from the harness, and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-wsl-env",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "\u8ba9 DeepSeek Harness \u4ee5 WSL \u5b50\u7cfb\u7edf\u4f5c\u4e3a\u4f1a\u8bdd\u7684\u6267\u884c\u73af\u5883\uff1ashell\u3001\u6587\u4ef6\u8bfb\u5199\u4e0e\u547d\u4ee4\u6267\u884c\u5747\u5728\u5b50\u7cfb\u7edf\u5185\u5b8c\u6210\uff0c\u76ee\u5f55\u9009\u62e9\u5668\u4e0e GUI \u7ec8\u7aef\u540c\u6837\u5b9a\u4f4d\u5230\u5b50\u7cfb\u7edf\u8def\u5f84\uff0c\u5e76\u901a\u8fc7\u5b50\u7cfb\u7edf\u5185\u7684 bubblewrap \u5bf9\u547d\u4ee4\u8fdb\u884c\u6c99\u7bb1\u9694\u79bb",
5
5
  "keywords": [
6
6
  "deepseek-harness",
@@ -75,10 +75,6 @@
75
75
  },
76
76
  "./subprocess/locale/en.json": "./locale/subprocess/en.json",
77
77
  "./subprocess/locale/zh.json": "./locale/subprocess/zh.json",
78
- "./workspace-files-wsl": {
79
- "types": "./lib/workspace-files-wsl.d.ts",
80
- "default": "./lib/workspace-files-wsl.js"
81
- },
82
78
  "./wsl": {
83
79
  "types": "./lib/wsl.d.ts",
84
80
  "default": "./lib/wsl.js"
@@ -106,6 +102,7 @@
106
102
  "docs/UPSTREAM-FSIO-EXPORT.md",
107
103
  "docs/UPSTREAM-TERMINAL-TITLE.md",
108
104
  "docs/ARCHITECTURE.md",
105
+ "docs/ROADMAP.md",
109
106
  "CHANGELOG.md",
110
107
  "CONTRIBUTING.md",
111
108
  "SECURITY.md",
@@ -119,9 +116,9 @@
119
116
  "lint:types": "tsc --noEmit",
120
117
  "build": "node scripts/build.mjs",
121
118
  "lint:build": "node test/build-freshness.mjs",
122
- "test:coverage": "node --test --experimental-test-coverage --test-coverage-lines=85 --test-coverage-branches=84 --test-coverage-functions=70 test/agent-exec.test.mjs test/agent-fs-script.test.mjs test/agent-protocol.test.mjs test/agent.test.mjs test/fs-substrate.test.mjs test/fs.boot.test.mjs test/workspace-files-wsl.test.mjs test/composition.test.mjs test/provider.test.mjs test/file-reference-wsl.test.mjs test/fsio-agent.test.mjs test/fsio-text.test.mjs test/git-route.test.mjs test/listing.test.mjs test/paths.test.mjs test/ports.test.mjs test/preset-choice.test.mjs test/probe-cache.test.mjs test/preflight.test.mjs test/sandbox.test.mjs test/search-exec.test.mjs test/search-route.test.mjs test/shell.test.mjs test/terminal-activity.test.mjs test/terminal.test.mjs test/watcher.test.mjs test/wsl.test.mjs test/fs-decisions.test.mjs test/session-scope.test.mjs test/session-runtime.test.mjs test/native-agent.test.mjs test/peers.test.mjs",
119
+ "test:coverage": "node --test --experimental-test-coverage --test-coverage-lines=85 --test-coverage-branches=84 --test-coverage-functions=70 test/agent-exec.test.mjs test/agent-fs-script.test.mjs test/agent-protocol.test.mjs test/agent.test.mjs test/fs-substrate.test.mjs test/fs.boot.test.mjs test/composition.test.mjs test/provider.test.mjs test/file-reference-wsl.test.mjs test/fsio-agent.test.mjs test/fsio-text.test.mjs test/git-route.test.mjs test/listing.test.mjs test/paths.test.mjs test/ports.test.mjs test/preset-choice.test.mjs test/probe-cache.test.mjs test/preflight.test.mjs test/sandbox.test.mjs test/search-exec.test.mjs test/search-route.test.mjs test/shell.test.mjs test/terminal-activity.test.mjs test/terminal.test.mjs test/watcher.test.mjs test/wsl.test.mjs test/fs-decisions.test.mjs test/session-scope.test.mjs test/session-runtime.test.mjs test/native-agent.test.mjs test/peers.test.mjs test/agent-shared.test.mjs",
123
120
  "test:syntax": "node test/syntax.mjs",
124
- "test:unit": "node --test test/fs.boot.test.mjs test/fsio-text.test.mjs test/workspace-files-wsl.test.mjs test/composition.test.mjs test/provider.test.mjs test/file-reference-wsl.test.mjs test/paths.test.mjs test/listing.test.mjs test/shell.test.mjs test/terminal.test.mjs test/probe-cache.test.mjs test/preflight.test.mjs test/preset-choice.test.mjs test/sandbox.test.mjs test/agent-protocol.test.mjs test/agent.test.mjs test/agent-fs-script.test.mjs test/fsio-agent.test.mjs test/fs-substrate.test.mjs test/agent-exec.test.mjs test/ports.test.mjs test/watcher.test.mjs test/wsl.test.mjs test/fs-decisions.test.mjs test/git-route.test.mjs test/fsio-text.test.mjs test/search-route.test.mjs test/search-exec.test.mjs test/terminal-activity.test.mjs test/session-scope.test.mjs test/session-runtime.test.mjs test/native-agent.test.mjs test/peers.test.mjs",
121
+ "test:unit": "node --test test/fs.boot.test.mjs test/fsio-text.test.mjs test/composition.test.mjs test/provider.test.mjs test/file-reference-wsl.test.mjs test/paths.test.mjs test/listing.test.mjs test/shell.test.mjs test/terminal.test.mjs test/probe-cache.test.mjs test/preflight.test.mjs test/preset-choice.test.mjs test/sandbox.test.mjs test/agent-protocol.test.mjs test/agent.test.mjs test/agent-fs-script.test.mjs test/fsio-agent.test.mjs test/fs-substrate.test.mjs test/agent-exec.test.mjs test/ports.test.mjs test/watcher.test.mjs test/wsl.test.mjs test/fs-decisions.test.mjs test/git-route.test.mjs test/fsio-text.test.mjs test/search-route.test.mjs test/search-exec.test.mjs test/terminal-activity.test.mjs test/session-scope.test.mjs test/session-runtime.test.mjs test/native-agent.test.mjs test/peers.test.mjs test/agent-shared.test.mjs",
125
122
  "lint:style": "node test/style.mjs",
126
123
  "prepublishOnly": "pnpm test",
127
124
  "probe": "bash test/probe/run.sh",
@@ -165,7 +162,6 @@
165
162
  "@deepseek-ai/cordis": "4.0.5",
166
163
  "@deepseek-ai/cordis-plugin-include": "*",
167
164
  "@deepseek-ai/cordis-plugin-loader": "*",
168
- "@deepseek-ai/dsh-api-workspace-files": "0.2.1-rc.2",
169
165
  "@deepseek-ai/dsh-bash-local": "0.2.1-rc.2",
170
166
  "@deepseek-ai/dsh-fs": "0.2.1-rc.2",
171
167
  "@deepseek-ai/dsh-fs-local": "0.2.1-rc.2",
@@ -183,7 +179,6 @@
183
179
  },
184
180
  "peerDependencies": {
185
181
  "@deepseek-ai/cordis": "~4.0.5",
186
- "@deepseek-ai/dsh-api-workspace-files": "0.2.1-rc.2",
187
182
  "@deepseek-ai/dsh-bash-local": "0.2.1-rc.2",
188
183
  "@deepseek-ai/dsh-fs": "0.2.1-rc.2",
189
184
  "@deepseek-ai/dsh-fs-local": "0.2.1-rc.2",
@@ -198,9 +193,6 @@
198
193
  "@deepseek-ai/cordis": {
199
194
  "optional": true
200
195
  },
201
- "@deepseek-ai/dsh-api-workspace-files": {
202
- "optional": true
203
- },
204
196
  "@deepseek-ai/dsh-bash-local": {
205
197
  "optional": true
206
198
  },
@@ -1,136 +0,0 @@
1
- /**
2
- * Pure builders and parsers for the distro-routed `workspace-files` service —
3
- * the GUI file tree and previews for a `\\wsl.localhost\<distro>` workspace,
4
- * served from inside the distro instead of Windows-side 9P walks.
5
- *
6
- * Peer-free by construction (no DSH imports): the service subclass imports
7
- * this alongside the shipped `dsh-api-workspace-files` base class, and the
8
- * unit tests import it standalone.
9
- *
10
- * This is a TypeScript source built to `lib/workspace-files-route.js`; edit
11
- * THIS file and run `pnpm run build` — the artifact under `lib/` is generated,
12
- * and `pnpm test` fails when it drifts.
13
- *
14
- * @module dsh-plugin-wsl/workspace-files-route
15
- */
16
- /**
17
- * Translate one path-shaped value into distro coordinates: UNC identities map
18
- * onto their Linux paths, drive-letter paths onto their `/mnt/<drive>` mounts,
19
- * everything else passes through verbatim.
20
- *
21
- * @param value - the value to translate.
22
- * @returns the distro-side spelling.
23
- */
24
- export declare function distroPath(value: string): string;
25
- /**
26
- * Whether a workspace root belongs to a distro (and therefore routes
27
- * distro-side).
28
- *
29
- * @param workspaceRoot - the scope's workspace root, any coordinate system.
30
- * @returns true for a `\\wsl.localhost\<distro>` UNC root.
31
- */
32
- export declare function isDistroWorkspace(workspaceRoot: string): boolean;
33
- /**
34
- * The Linux working path for one request: UNC absolutes map onto their Linux
35
- * paths, POSIX absolutes and relative paths join onto the root.
36
- *
37
- * @param linuxRoot - the workspace root in Linux form.
38
- * @param path - the requested path, any coordinate system.
39
- * @returns the absolute Linux path.
40
- */
41
- export declare function linuxJoin(linuxRoot: string, path: string): string;
42
- /**
43
- * The record invocation for one path: line 1 is the no-follow kind (the
44
- * upstream lstat gate), line 2 the follow-stat fields (the upstream final
45
- * stat), tab-joined. The path is resolved and contained first — the GUI's
46
- * workspace is a boundary for every read, not just for listings.
47
- *
48
- * @param file - absolute Linux path.
49
- * @param root - the workspace root in Linux form (containment boundary).
50
- * @returns the agent argv.
51
- */
52
- export declare function statRecordArgv(file: string, root: string): string[];
53
- /**
54
- * Parse one stat record (kind + follow fields, tab-joined).
55
- *
56
- * @param line - the record line.
57
- * @returns the upstream kind spelling, the byte size, and the synthesized
58
- * opaque version.
59
- */
60
- export declare function parseStatRecord(line: string | Buffer): {
61
- kind: string;
62
- type: string;
63
- size: number;
64
- version: string;
65
- };
66
- /**
67
- * Map a `stat` kind onto the wire's kind token.
68
- *
69
- * @param kind - the `stat -c %F` spelling.
70
- * @returns the wire kind token.
71
- */
72
- export declare function wireKind(kind: string): string;
73
- /**
74
- * The read invocation for one line-paged text read: the stat record rides
75
- * stderr (the caller builds the stat half of the response), the page rides
76
- * stdout — one line beyond the page, so EOF is decidable from the yield.
77
- * `$1` is the file, `$2` the 1-based first line, `$3` the line count.
78
- *
79
- * @param file - absolute Linux path.
80
- * @param offset - first line to return (1-based).
81
- * @param limit - maximum lines to return.
82
- * @param root - the workspace root in Linux form (containment boundary).
83
- * @returns the agent argv.
84
- */
85
- export declare function pageArgv(file: string, offset: number, limit: number, root: string): string[];
86
- /**
87
- * The byte-window invocation: the stat record rides stderr, the window rides
88
- * stdout. `$1` is the file, `$2` the byte offset, `$3` the byte count.
89
- *
90
- * @param file - absolute Linux path.
91
- * @param offset - byte offset (0-based).
92
- * @param length - byte count.
93
- * @param root - the workspace root in Linux form (containment boundary).
94
- * @returns the agent argv.
95
- */
96
- export declare function bytesArgv(file: string, offset: number, length: number, root: string): string[];
97
- /**
98
- * Parse the stat record a read or byte-window relayed on stderr.
99
- *
100
- * @param stderr - the relay's raw stderr.
101
- * @returns the parsed stat record, or undefined when stderr carries none.
102
- */
103
- export declare function parseStatRecordFromStderr(stderr: string | Buffer): {
104
- kind: string;
105
- type: string;
106
- size: number;
107
- version: string;
108
- } | undefined;
109
- /**
110
- * The listing invocation for one directory: resolve the final component
111
- * through symlinks, refuse paths outside the root and non-directories, then
112
- * list one more entry than the cap so truncation is decidable from the yield.
113
- * `-L` matches the upstream child semantics (a symlink to a directory lists
114
- * as a directory, a broken link drops out); `-p` marks directories with the
115
- * trailing `/` the parser keys on.
116
- *
117
- * @param linuxRoot - the workspace root in Linux form (containment boundary).
118
- * @param linuxDir - the directory to list.
119
- * @param maxEntries - the caller's entry cap.
120
- * @returns the agent argv.
121
- */
122
- export declare function listDistroArgv(linuxRoot: string, linuxDir: string, maxEntries: number): string[];
123
- /**
124
- * Parse one capped `ls -1ALp` listing into wire entries.
125
- *
126
- * @param stdout - the listing's raw stdout.
127
- * @param maxEntries - the entry cap the caller configured.
128
- * @returns at most `maxEntries` entries and whether the level had more.
129
- */
130
- export declare function parseDirListing(stdout: string | Buffer, maxEntries: number): {
131
- entries: Array<{
132
- name: string;
133
- type: "file" | "directory";
134
- }>;
135
- truncated: boolean;
136
- };