dsh-wsl-workspace 0.7.2 → 0.7.4

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/index.js CHANGED
@@ -1,10 +1,10 @@
1
- import { defaultDistro, isAbsoluteLinuxPath, isValidWslUsername, joinUnc, listDistros, mntToWindowsPath, normalizeLinuxPath, parseWslUnc, uncToLinux } from "./wsl-CECXiGZH.js";
2
- import { canonicalWslUnc, getWindowsWorkspace, getWorkspaceUsername, listWorkspaceKeys, registerWindowsWorkspace, setWorkspaceUsername } from "./wsl-credentials-BJXc9tfi.js";
3
- import { resolveLinuxSymlinks } from "./links-BQxdJOMy.js";
1
+ import { defaultDistro, isAbsoluteLinuxPath, isValidWslUsername, joinUnc, listDistros, mntToWindowsPath, normalizeLinuxPath, parseWslUnc, uncToLinux } from "./wsl-BTA7XT8X.js";
2
+ import { canonicalWslUnc, getWindowsWorkspace, getWorkspaceUsername, listWorkspaceKeys, registerWindowsWorkspace, setWorkspaceUsername } from "./wsl-credentials-gFHTqaiN.js";
3
+ import { resolveLinuxSymlinks } from "./links-DQ8GgpMj.js";
4
4
  import z from "@deepseek-ai/schemastery";
5
5
  import { cpSync, existsSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
6
- import { fileURLToPath } from "node:url";
7
- import { dirname, join, posix } from "node:path";
6
+ import { fileURLToPath, pathToFileURL } from "node:url";
7
+ import { dirname, isAbsolute, join, posix } from "node:path";
8
8
  import { homedir } from "node:os";
9
9
  import { readFile, readdir, stat } from "node:fs/promises";
10
10
 
@@ -1039,6 +1039,13 @@ const DEFAULT_ROUTE = "/wsl-workspace/api";
1039
1039
  * preset.yml text verbatim, so the plugin writes one bilingual string so
1040
1040
  * both locales can identify each variant. Custom presets keep their own
1041
1041
  * name.
1042
+ *
1043
+ * This table is consulted before anything the release publishes, so every id
1044
+ * the mode has ever carried needs an entry. `code` was the id up to
1045
+ * `0.1.0-rc.8` and `ptc` is the id from `0.1.1` on; without the `ptc` entry the
1046
+ * lookup returned undefined and the variant fell through to the release's own
1047
+ * display name — which `0.1.7` does not publish at all, leaving the mode named
1048
+ * `WSL · ptc` with the generic description `WSL execution world for ptc: …`.
1042
1049
  */
1043
1050
  const MODE_DISPLAY_LABELS = {
1044
1051
  standard: {
@@ -1049,6 +1056,10 @@ const MODE_DISPLAY_LABELS = {
1049
1056
  en: "Code mode",
1050
1057
  zh: "PTC 模式"
1051
1058
  },
1059
+ ptc: {
1060
+ en: "PTC mode",
1061
+ zh: "PTC 模式"
1062
+ },
1052
1063
  minimal: {
1053
1064
  en: "Minimal mode",
1054
1065
  zh: "极简模式"
@@ -1286,6 +1297,81 @@ async function dispatch(method, params) {
1286
1297
  }
1287
1298
  }
1288
1299
  /**
1300
+ * Read one preset's declared composition across both roster generations.
1301
+ * @param agentPresets - the roster face.
1302
+ * @param preset - the roster entry to read.
1303
+ * @returns the composition text, plus the display name when the face publishes it.
1304
+ */
1305
+ async function readPresetComposition(agentPresets, preset) {
1306
+ if (typeof agentPresets.readDocument === "function") {
1307
+ const document = await agentPresets.readDocument(preset.id);
1308
+ return document.name === void 0 ? { content: document.content } : {
1309
+ content: document.content,
1310
+ name: document.name
1311
+ };
1312
+ }
1313
+ if (typeof agentPresets.read === "function") return { content: await agentPresets.read(preset.id) };
1314
+ throw new Error(`agentPresets: this DSH release exposes neither readDocument() nor read() (preset "${preset.id}")`);
1315
+ }
1316
+ /**
1317
+ * Parse one transformed composition back into declaration rows.
1318
+ *
1319
+ * The composition is the entry-list YAML dialect, whose `!!js` scalars are
1320
+ * expression nodes the Loader evaluates when it activates the row.
1321
+ * `@deepseek-ai/cordis-plugin-include` exports `entryListSchema` precisely so
1322
+ * config tooling can round-trip that dialect, and it is the same schema the
1323
+ * harness parses preset patches with. Parsing the text back is what lets the
1324
+ * (text-level) WSL transform keep working unchanged now that a variant is a
1325
+ * declaration row instead of a directory of YAML.
1326
+ *
1327
+ * Both modules are resolved at call time rather than at module load: they are
1328
+ * Host-provided (`dsh` depends on `cordis-plugin-include`, which depends on
1329
+ * `js-yaml`), and a release that ever drops them must fail this one variant
1330
+ * rather than refuse to load the whole plugin.
1331
+ * @param content - the variant composition text.
1332
+ * @returns the declaration's plugin rows.
1333
+ */
1334
+ async function parseVariantComposition(content) {
1335
+ const includeSpecifier = "@deepseek-ai/cordis-plugin-include";
1336
+ const yamlSpecifier = "js-yaml";
1337
+ const [include, yamlModule] = await Promise.all([import(includeSpecifier), import(yamlSpecifier)]);
1338
+ const load = yamlModule.load ?? yamlModule.default?.load;
1339
+ if (typeof load !== "function") throw new Error("js-yaml: no load() export");
1340
+ const rows = load(content, { schema: include.entryListSchema });
1341
+ if (!Array.isArray(rows)) throw new Error("the transformed composition did not parse as an entry list");
1342
+ return toImportableSpecifiers(rows);
1343
+ }
1344
+ /**
1345
+ * Rewrite every absolute local module specifier as a `file:` URL.
1346
+ *
1347
+ * The generated world names THIS package's built providers (`shell.js`,
1348
+ * `fs.js`, …) by absolute path. That is what the directory mechanism needed:
1349
+ * those rows sat in an Include-backed tree, and the boot-time Include
1350
+ * translates an absolute path into a `file:` URL before importing it. A preset
1351
+ * mounted from a *declaration* is loaded by the registry's own entry tree,
1352
+ * which has no such translation — handing it `C:/…/lib/shell.js` leaves those
1353
+ * rows without a fiber, the audit reports them "never started", and the whole
1354
+ * variant is refused. Rewriting the specifier is therefore part of adapting to
1355
+ * the declaration mechanism, not a change to what the variant mounts.
1356
+ *
1357
+ * Only `name` is touched: config values (the relay path handed to the PTY
1358
+ * backend, the interpreter path in `shellPath`) must stay native filesystem
1359
+ * paths. Group rows carry their children in a `config` array, so those are
1360
+ * walked too.
1361
+ * @param rows - the parsed declaration rows.
1362
+ * @returns the same rows with their module specifiers made importable.
1363
+ */
1364
+ function toImportableSpecifiers(rows) {
1365
+ const rewrite = (row) => {
1366
+ if (row === null || typeof row !== "object") return row;
1367
+ const entry = row;
1368
+ if (typeof entry.name === "string" && isAbsolute(entry.name)) entry.name = pathToFileURL(entry.name).href;
1369
+ if (Array.isArray(entry.config)) entry.config = entry.config.map(rewrite);
1370
+ return row;
1371
+ };
1372
+ return rows.map(rewrite);
1373
+ }
1374
+ /**
1289
1375
  * Publish a fully staged preset directory while preserving the last complete
1290
1376
  * variant if publication fails. Stable sibling names also let the next boot
1291
1377
  * recover an interrupted old-to-backup rename before doing new work.
@@ -1378,7 +1464,7 @@ async function waitForSubprocess(ctx) {
1378
1464
  return void 0;
1379
1465
  }
1380
1466
  /** Materialize one WSL variant per healthy source preset. */
1381
- async function materializeVariants(agentPresets, dshHome, paths, persistentShell) {
1467
+ async function materializeVariants(agentPresets, dshHome, paths, persistentShell, track) {
1382
1468
  const presets = await agentPresets.list();
1383
1469
  const userRoot = join(dshHome, ".agent-presets");
1384
1470
  const generated = /* @__PURE__ */ new Set();
@@ -1386,12 +1472,25 @@ async function materializeVariants(agentPresets, dshHome, paths, persistentShell
1386
1472
  if (preset.broken !== void 0) continue;
1387
1473
  if (isWslVariantId(preset.id)) continue;
1388
1474
  const variantId = variantIdFor(preset.id);
1389
- const source = await agentPresets.read(preset.id);
1390
- const transformed = transformPresetForWsl(source, paths.shell, paths.fs, persistentShell ? {
1475
+ const composition = await readPresetComposition(agentPresets, preset);
1476
+ const transformed = transformPresetForWsl(composition.content, paths.shell, paths.fs, persistentShell ? {
1391
1477
  relayPath: paths.relay,
1392
1478
  nodePath: paths.node,
1393
1479
  sandboxPath: paths.sandbox
1394
1480
  } : void 0, paths.search, paths.jobs);
1481
+ if (typeof agentPresets.register === "function") {
1482
+ const plugins = await parseVariantComposition(transformed);
1483
+ const declaration = {
1484
+ id: variantId,
1485
+ name: variantName(preset.id, composition.name ?? preset.name ?? preset.id),
1486
+ description: variantDescription(preset.id),
1487
+ ...preset.order === void 0 ? {} : { order: preset.order },
1488
+ plugins
1489
+ };
1490
+ track(await agentPresets.register(declaration));
1491
+ continue;
1492
+ }
1493
+ if (preset.path === void 0) throw new Error(`agentPresets: roster entry "${preset.id}" carries no path on this release`);
1395
1494
  const dir = join(userRoot, variantId);
1396
1495
  const staging = `${dir}.staging`;
1397
1496
  rmSync(staging, {
@@ -1419,17 +1518,11 @@ async function materializeVariants(agentPresets, dshHome, paths, persistentShell
1419
1518
  publishVariant(staging, dir);
1420
1519
  generated.add(variantId);
1421
1520
  }
1422
- for (const entry of readdirSync(userRoot, { withFileTypes: true })) {
1521
+ if (existsSync(userRoot)) for (const entry of readdirSync(userRoot, { withFileTypes: true })) {
1423
1522
  if (!entry.isDirectory()) continue;
1424
- if (entry.name === "wsl") {
1425
- rmSync(join(userRoot, entry.name), {
1426
- recursive: true,
1427
- force: true
1428
- });
1429
- continue;
1430
- }
1431
- if (!/^wsl-[a-z0-9-]+$/.test(entry.name)) continue;
1432
- if (!generated.has(entry.name)) rmSync(join(userRoot, entry.name), {
1523
+ if (!/^wsl(-[a-z0-9-]+)?$/.test(entry.name)) continue;
1524
+ if (generated.has(entry.name)) continue;
1525
+ rmSync(join(userRoot, entry.name), {
1433
1526
  recursive: true,
1434
1527
  force: true
1435
1528
  });
@@ -1463,6 +1556,17 @@ function apply(ctx, config) {
1463
1556
  const nodePath = process.execPath.replace(/\\/g, "/");
1464
1557
  const agentPresets = ctx.get("agentPresets");
1465
1558
  if (agentPresets !== void 0) ctx.effect(() => {
1559
+ let stopped = false;
1560
+ const disposers = [];
1561
+ const track = (dispose) => {
1562
+ if (typeof dispose !== "function") return;
1563
+ const retire = dispose;
1564
+ if (stopped) {
1565
+ Promise.resolve(retire()).catch(() => {});
1566
+ return;
1567
+ }
1568
+ disposers.push(retire);
1569
+ };
1466
1570
  (async () => {
1467
1571
  const persistentShell = await supportsPersistentShell(ctx);
1468
1572
  await materializeVariants(agentPresets, dshHome, {
@@ -1473,11 +1577,14 @@ function apply(ctx, config) {
1473
1577
  sandbox: sandboxPath,
1474
1578
  search: searchPath,
1475
1579
  jobs: jobsPath
1476
- }, persistentShell);
1580
+ }, persistentShell, track);
1477
1581
  })().catch((error) => {
1478
1582
  console.error(`dsh-wsl-workspace: WSL preset-variant generation failed: ${messageOf(error)}`);
1479
1583
  });
1480
- return () => {};
1584
+ return () => {
1585
+ stopped = true;
1586
+ for (const retire of disposers.splice(0, disposers.length)) Promise.resolve(retire()).catch(() => {});
1587
+ };
1481
1588
  }, "dsh-wsl-workspace: WSL preset variants");
1482
1589
  const skills = ctx.get("skills");
1483
1590
  if (skills !== void 0 && typeof skills.registerProvider === "function") ctx.effect(() => skills.registerProvider((control) => new WslSkillsProvider(control)), "dsh-wsl-workspace: WSL workspace skills provider");