dsh-wsl-desktop 0.7.1 → 0.7.2
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/client.js +156 -7
- package/lib/wsl/paths.js +12 -1
- package/lib/wsl/pty.js +89 -9
- package/lib/wsl/subprocess.js +2 -2
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -38,6 +38,34 @@ window.__ModuleLoader__.load({
|
|
|
38
38
|
'M5.54492 2.06738', // post-refactor ProjectAddOutlineArtwork
|
|
39
39
|
]
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* The same trigger, named by sprite symbol instead of path data.
|
|
43
|
+
*
|
|
44
|
+
* Desktop 0.2.1-rc3 (ui-primitives `c3eead771d`) moved every product icon
|
|
45
|
+
* into the document icon sprite: the header button now renders
|
|
46
|
+
* `<svg data-icon="dsw-icon-project-add-outline-regular"><use href="#…"/></svg>`
|
|
47
|
+
* with NO `<path>` child anywhere. The geometry scan above therefore matched
|
|
48
|
+
* nothing, the anchor read as absent, and the W button stood down on every
|
|
49
|
+
* 0.2.1-rc3 desktop. The glyph name is the locale-independent identity in
|
|
50
|
+
* that generation, exactly as the path prefix was in the previous one, and a
|
|
51
|
+
* theme may replace the artwork without renaming the symbol — so the name
|
|
52
|
+
* survives the change that broke the geometry.
|
|
53
|
+
*/
|
|
54
|
+
const TRIGGER_ICON_GLYPHS = ['project-add-outline']
|
|
55
|
+
|
|
56
|
+
/** Sprite symbol id shape, as `iconSymbolId` composes it: `dsw-icon-<glyph>-<weight>`. */
|
|
57
|
+
const SPRITE_ICON_ID = /^#?dsw-icon-(.+)-(?:regular|medium)$/
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The row glyphs whose artwork is FILL-only, as sprite names.
|
|
61
|
+
*
|
|
62
|
+
* Only the fallback for a sprite symbol that is not in the document yet; the
|
|
63
|
+
* live read (`isSolidIcon`) prefers the symbol's own geometry. Versions
|
|
64
|
+
* qualify the id (`symbolId(glyph, weight)`), so the suffix is not part of a
|
|
65
|
+
* glyph name and the match is on the glyph alone.
|
|
66
|
+
*/
|
|
67
|
+
const SOLID_GLYPHS = ['folder-open']
|
|
68
|
+
|
|
41
69
|
/** Namespace of every SVG node this half creates. */
|
|
42
70
|
const SVG_NS = 'http://www.w3.org/2000/svg'
|
|
43
71
|
|
|
@@ -1476,15 +1504,68 @@ window.__ModuleLoader__.load({
|
|
|
1476
1504
|
/** Whether the missing-workspace-source warning was already printed. */
|
|
1477
1505
|
let warnedWorkspaceSource = false
|
|
1478
1506
|
|
|
1479
|
-
/**
|
|
1480
|
-
|
|
1507
|
+
/**
|
|
1508
|
+
* The sprite glyph one `<use>` reference names, or null.
|
|
1509
|
+
*
|
|
1510
|
+
* Accepts both the bare symbol id and the `#`-prefixed href form, and both
|
|
1511
|
+
* `href` and the legacy `xlink:href` spelling, because an icon may render
|
|
1512
|
+
* either. Returning the GLYPH (not the whole id) lets the trigger list stay
|
|
1513
|
+
* a list of glyph names, which is what a desktop generation actually fixes.
|
|
1514
|
+
* @param {Element} node - a `<use>` element.
|
|
1515
|
+
* @returns {string | null} the glyph name, or null when it is not a sprite ref.
|
|
1516
|
+
*/
|
|
1517
|
+
function spriteGlyphOf(node) {
|
|
1518
|
+
const ref = node.getAttribute('href')
|
|
1519
|
+
?? node.getAttributeNS('http://www.w3.org/1999/xlink', 'href')
|
|
1520
|
+
if (ref === null) return null
|
|
1521
|
+
return SPRITE_ICON_ID.exec(ref)?.[1] ?? null
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
/**
|
|
1525
|
+
* The candidate trigger buttons carrying one generation's icon identity.
|
|
1526
|
+
*
|
|
1527
|
+
* Each generation is ONE document-level selector rather than a walk over
|
|
1528
|
+
* every button: `findTrigger` re-runs on every animation frame whenever the
|
|
1529
|
+
* anchor is missing, so a per-button subtree query would turn a single scan
|
|
1530
|
+
* into N of them exactly when the header is already in trouble.
|
|
1531
|
+
* @param {string} selector - the icon nodes this generation renders.
|
|
1532
|
+
* @param {(node: Element) => boolean} identifies - whether one icon is the trigger's.
|
|
1533
|
+
* @returns {HTMLElement[]} the distinct buttons owning a matching icon.
|
|
1534
|
+
*/
|
|
1535
|
+
function triggerCandidates(selector, identifies) {
|
|
1481
1536
|
const matches = []
|
|
1482
|
-
for (const
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
const trigger = path.closest('button')
|
|
1537
|
+
for (const icon of document.querySelectorAll(selector)) {
|
|
1538
|
+
if (!identifies(icon)) continue
|
|
1539
|
+
const trigger = icon.closest('button')
|
|
1486
1540
|
if (trigger !== null && !matches.includes(trigger)) matches.push(trigger)
|
|
1487
1541
|
}
|
|
1542
|
+
return matches
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
/** Whether one inline path carries the add-workspace geometry. */
|
|
1546
|
+
function isTriggerPath(path) {
|
|
1547
|
+
const d = path.getAttribute('d')
|
|
1548
|
+
return TRIGGER_ICON_PATHS.some((geometry) => d.startsWith(geometry))
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
/**
|
|
1552
|
+
* Whether one icon node is the add-workspace artwork, in either generation.
|
|
1553
|
+
*
|
|
1554
|
+
* A sprite icon is identified by `data-icon` (the id the component was
|
|
1555
|
+
* CONSTRUCTED with, which a theme replacement does not change) and, failing
|
|
1556
|
+
* that, by the `<use>` reference it renders.
|
|
1557
|
+
* @param {Element} icon - an inline path or a sprite `<svg>`/`<use>`.
|
|
1558
|
+
* @returns {boolean} true when it is the trigger's artwork.
|
|
1559
|
+
*/
|
|
1560
|
+
function isTriggerIcon(icon) {
|
|
1561
|
+
if (icon.tagName === 'path') return isTriggerPath(icon)
|
|
1562
|
+
const declared = icon.tagName === 'svg' ? icon.getAttribute('data-icon') : null
|
|
1563
|
+
const glyph = declared !== null ? SPRITE_ICON_ID.exec(declared)?.[1] : spriteGlyphOf(icon)
|
|
1564
|
+
return glyph !== undefined && glyph !== null && TRIGGER_ICON_GLYPHS.includes(glyph)
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
/** Pick the trigger out of one generation's candidates, or null. */
|
|
1568
|
+
function pickTrigger(matches) {
|
|
1488
1569
|
if (matches.length <= 1) return matches[0] ?? null
|
|
1489
1570
|
// More than one control carries the geometry. The shipped trigger is the
|
|
1490
1571
|
// one inside a `*_headerActions` cluster — the CSS-module local name is
|
|
@@ -1494,6 +1575,24 @@ window.__ModuleLoader__.load({
|
|
|
1494
1575
|
return matches.find((trigger) => hasHeaderActionsParent(trigger)) ?? null
|
|
1495
1576
|
}
|
|
1496
1577
|
|
|
1578
|
+
/**
|
|
1579
|
+
* Find the shipped "add workspace" trigger in either desktop generation.
|
|
1580
|
+
*
|
|
1581
|
+
* The sprite generation is scanned FIRST because it is the newer one: on a
|
|
1582
|
+
* desktop that renders it, that single scan is the whole cost. Only a
|
|
1583
|
+
* desktop with no sprite icon anywhere falls through to the geometry scan,
|
|
1584
|
+
* which is what every release before 0.2.1-rc3 needed.
|
|
1585
|
+
* @returns {HTMLElement | null} the anchor, or null when this desktop has none.
|
|
1586
|
+
*/
|
|
1587
|
+
function findTrigger() {
|
|
1588
|
+
const sprite = triggerCandidates('svg[data-icon], use', isTriggerIcon)
|
|
1589
|
+
// A non-empty sprite result is this desktop's answer even when it is
|
|
1590
|
+
// ambiguous: `pickTrigger` returns null there to mean "stand down", and
|
|
1591
|
+
// falling through would let an unrelated inline icon pick the anchor.
|
|
1592
|
+
if (sprite.length > 0) return pickTrigger(sprite)
|
|
1593
|
+
return pickTrigger(triggerCandidates('svg > path[d]', isTriggerIcon))
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1497
1596
|
/**
|
|
1498
1597
|
* Whether one trigger sits in the shipped trailing action cluster.
|
|
1499
1598
|
* @param {HTMLElement} trigger - a candidate trigger button.
|
|
@@ -1637,7 +1736,7 @@ window.__ModuleLoader__.load({
|
|
|
1637
1736
|
// artwork fills its paths, the collapsed one strokes them. A rebuild is
|
|
1638
1737
|
// owed whenever that flips, which is exactly what expanding a workspace
|
|
1639
1738
|
// does to this span.
|
|
1640
|
-
const solid = shipped
|
|
1739
|
+
const solid = isSolidIcon(shipped)
|
|
1641
1740
|
const weight = solid ? 'solid' : 'outline'
|
|
1642
1741
|
if (mine !== null && mine.dataset.wslCloudWeight !== weight) {
|
|
1643
1742
|
mine.remove()
|
|
@@ -1648,6 +1747,56 @@ window.__ModuleLoader__.load({
|
|
|
1648
1747
|
}
|
|
1649
1748
|
}
|
|
1650
1749
|
|
|
1750
|
+
/**
|
|
1751
|
+
* The sprite symbol an icon element references, as a bare symbol id.
|
|
1752
|
+
*
|
|
1753
|
+
* `data-icon` is the id the icon COMPONENT was built with, so it keeps the
|
|
1754
|
+
* shipped symbol even when a theme points the `<use>` href at its own
|
|
1755
|
+
* artwork; the href is the fallback for markup that omits `data-icon`.
|
|
1756
|
+
* @param {Element} icon - one icon `<svg>`.
|
|
1757
|
+
* @returns {string | null} the symbol id without `#`, or null.
|
|
1758
|
+
*/
|
|
1759
|
+
function spriteSymbolOf(icon) {
|
|
1760
|
+
const declared = icon.getAttribute('data-icon')
|
|
1761
|
+
if (typeof declared === 'string' && declared !== '') return declared.replace(/^#/, '')
|
|
1762
|
+
for (const use of icon.querySelectorAll('use')) {
|
|
1763
|
+
const ref = use.getAttribute('href')
|
|
1764
|
+
?? use.getAttributeNS('http://www.w3.org/1999/xlink', 'href')
|
|
1765
|
+
if (typeof ref === 'string' && ref !== '') return ref.replace(/^#/, '')
|
|
1766
|
+
}
|
|
1767
|
+
return null
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
/**
|
|
1771
|
+
* Whether one workspace row icon renders the FILL-only artwork.
|
|
1772
|
+
*
|
|
1773
|
+
* The collapsed row strokes its folder outline and the expanded one fills
|
|
1774
|
+
* it, and the cloud has to match whichever is showing. Up to 0.2.1-rc2 the
|
|
1775
|
+
* artwork was inline, so a stroked `<path>` was the tell. From 0.2.1-rc3 the
|
|
1776
|
+
* icon is a sprite reference with NO path child anywhere: the weight then
|
|
1777
|
+
* belongs to the SYMBOL in the document sprite, not to the `<svg>` naming
|
|
1778
|
+
* it. Reading only the inline shape (as this did) made every sprite row look
|
|
1779
|
+
* fill-only, so a collapsed row got the solid cloud.
|
|
1780
|
+
* @param {Element | null} icon - the shipped folder icon, if the row renders one.
|
|
1781
|
+
* @returns {boolean} true when the icon is the fill-only artwork.
|
|
1782
|
+
*/
|
|
1783
|
+
function isSolidIcon(icon) {
|
|
1784
|
+
if (icon === null) return false
|
|
1785
|
+
// Inline generation: the artwork declares its own weight.
|
|
1786
|
+
if (icon.querySelector('path[d]') !== null) return icon.querySelector('path[stroke]') === null
|
|
1787
|
+
const symbolId = spriteSymbolOf(icon)
|
|
1788
|
+
if (symbolId === null) return false
|
|
1789
|
+
const symbol = document.getElementById(symbolId)
|
|
1790
|
+
// Sprite generation, symbol present: judge the artwork the browser paints,
|
|
1791
|
+
// so a theme's own geometry is measured rather than assumed.
|
|
1792
|
+
if (symbol !== null) return symbol.querySelector('path[stroke]') === null
|
|
1793
|
+
// Sprite generation, symbol not in the document (a theme may mount its
|
|
1794
|
+
// own sprite later): fall back to the glyph name, which is the fill-only
|
|
1795
|
+
// identity in the shipped table.
|
|
1796
|
+
const glyph = SPRITE_ICON_ID.exec(`#${symbolId}`)?.[1]
|
|
1797
|
+
return glyph !== undefined && glyph !== null && SOLID_GLYPHS.includes(glyph)
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1651
1800
|
/**
|
|
1652
1801
|
* Whether one observed mutation could have changed a workspace row's markup.
|
|
1653
1802
|
*
|
package/lib/wsl/paths.js
CHANGED
|
@@ -20,8 +20,19 @@ const UNC_HOSTS = new Set(['wsl.localhost', 'wsl$'])
|
|
|
20
20
|
* directory, so `\\wsl.localhost\..\<path>` would hand the share a traversal
|
|
21
21
|
* spelling. No real distribution name is all dots, and `wsl.exe -d ..` cannot be
|
|
22
22
|
* a legal invocation, so the lookahead costs nothing legitimate.
|
|
23
|
+
*
|
|
24
|
+
* A LEADING HYPHEN is refused for a different reason: the value becomes the token
|
|
25
|
+
* after `wsl.exe -d`, and an argv element that begins with `-` is not a provably
|
|
26
|
+
* safe option VALUE. Measured on wsl.exe: `-d --cd --cd /x` and `-d -dfoo` both
|
|
27
|
+
* fail with WSL_E_DISTRO_NOT_FOUND rather than re-reading the token as an option,
|
|
28
|
+
* so the practical outcome is a clean refusal and no command — but the invariant
|
|
29
|
+
* `buildWslExecArgv` states (option values validated against our own grammars, so
|
|
30
|
+
* exec-path safety never depends on wsl.exe's undocumented tokenization) was not
|
|
31
|
+
* actually enforced for this value. The sibling grammar LINUX_USER below already
|
|
32
|
+
* forces a safe first character, so `-u` was refused while `-d` was not; this
|
|
33
|
+
* lookahead makes the pair agree. No real distribution name begins with `-`.
|
|
23
34
|
*/
|
|
24
|
-
export const DISTRO_NAME = /^(?!\.+$)[A-Za-z0-9._-]+$/
|
|
35
|
+
export const DISTRO_NAME = /^(?!-)(?!\.+$)[A-Za-z0-9._-]+$/
|
|
25
36
|
|
|
26
37
|
/** A Linux user name that is safe to pass as a single wsl.exe -u option value. */
|
|
27
38
|
export const LINUX_USER = /^[A-Za-z0-9._][A-Za-z0-9._-]*\$?$/
|
package/lib/wsl/pty.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* @module dsh-wsl-desktop/wsl/pty
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import { appendFileSync } from 'node:fs'
|
|
18
|
+
import { appendFileSync, renameSync, statSync } from 'node:fs'
|
|
19
19
|
import { randomUUID } from 'node:crypto'
|
|
20
20
|
import { homedir } from 'node:os'
|
|
21
21
|
import { join } from 'node:path'
|
|
@@ -48,9 +48,25 @@ const DEFAULT_ROWS = 24
|
|
|
48
48
|
const verifiedDistros = new Set()
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
51
|
+
* Ceiling for the opt-in terminal debug log, and the size at which it rotates.
|
|
52
|
+
*
|
|
53
|
+
* The doc comment on `termLog` used to call the sink "bounded" while the body was a
|
|
54
|
+
* bare `appendFileSync` with no cap, no rotation and no redaction: a host that left
|
|
55
|
+
* `WSL_DESKTOP_TERMINAL_DEBUG=1` set grew the file without limit. Rotation is
|
|
56
|
+
* one-generation (`.1`), which bounds the pair at ~2x this figure and needs no
|
|
57
|
+
* bookkeeping beyond a `statSync`.
|
|
58
|
+
*/
|
|
59
|
+
const TERMINAL_LOG_MAX_BYTES = 5 * 1024 * 1024
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Append one diagnostic line to the terminal debug log (bounded and rotated).
|
|
63
|
+
* Temporary instrumentation for the live GUI-path terminal failure; harmless if
|
|
64
|
+
* the file cannot be written.
|
|
65
|
+
*
|
|
66
|
+
* Callers must NOT hand this full argv vectors. A command line can carry a secret
|
|
67
|
+
* (`tool --token=…`), and this file is plaintext under `$DSH_HOME`, readable by any
|
|
68
|
+
* host-side reader of the profile. Describe the argv by its length and program
|
|
69
|
+
* name instead — see `describeArgv`.
|
|
54
70
|
* @param {string} message - the diagnostic line.
|
|
55
71
|
*/
|
|
56
72
|
export function termLog(message) {
|
|
@@ -58,12 +74,40 @@ export function termLog(message) {
|
|
|
58
74
|
// without bound on every terminal allocation.
|
|
59
75
|
if (process.env.WSL_DESKTOP_TERMINAL_DEBUG !== '1') return
|
|
60
76
|
try {
|
|
61
|
-
|
|
77
|
+
const path = join(process.env.DSH_HOME ?? join(homedir(), '.dsh'), 'wsl-desktop-terminal-debug.log')
|
|
78
|
+
// Rotate BEFORE appending, so the file cannot cross the ceiling even by one
|
|
79
|
+
// line: the check is "would this write exceed it", not "has it already".
|
|
80
|
+
try {
|
|
81
|
+
const { size } = statSync(path)
|
|
82
|
+
if (size >= TERMINAL_LOG_MAX_BYTES) renameSync(path, `${path}.1`)
|
|
83
|
+
} catch {
|
|
84
|
+
// Absent (first write) or unreadable: appending is still safe.
|
|
85
|
+
}
|
|
86
|
+
appendFileSync(path, `${new Date().toISOString()} ${message}\n`)
|
|
62
87
|
} catch {
|
|
63
88
|
// Diagnostics must never break allocation.
|
|
64
89
|
}
|
|
65
90
|
}
|
|
66
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Describe an argv vector without reproducing it.
|
|
94
|
+
*
|
|
95
|
+
* The program name (argv[0]) and the element count are enough to tell "the bridge
|
|
96
|
+
* got the right shape" from "it got a different one", which is what these log lines
|
|
97
|
+
* exist for. The remaining elements are where secrets live, so they are never
|
|
98
|
+
* written: the count preserves the shape evidence and nothing else is lost that the
|
|
99
|
+
* log was actually used for.
|
|
100
|
+
* @param {readonly string[] | undefined} argv - the vector to describe.
|
|
101
|
+
* @returns {string} a redacted description, e.g. `bash (3 args)`.
|
|
102
|
+
*/
|
|
103
|
+
export function describeArgv(argv) {
|
|
104
|
+
if (!Array.isArray(argv) || argv.length === 0) return '(no argv)'
|
|
105
|
+
const program = String(argv[0])
|
|
106
|
+
// The program itself may be a full path; its basename is the informative part.
|
|
107
|
+
const name = program.split(/[\\/]/).pop() ?? program
|
|
108
|
+
return `${name} (${argv.length - 1} args)`
|
|
109
|
+
}
|
|
110
|
+
|
|
67
111
|
/**
|
|
68
112
|
* Report a diagnostic that must be VISIBLE without the opt-in debug log.
|
|
69
113
|
*
|
|
@@ -782,7 +826,7 @@ export async function spawnWslTerminal({
|
|
|
782
826
|
return false
|
|
783
827
|
},
|
|
784
828
|
))
|
|
785
|
-
termLog(`allocate: distro=${plan.distro} linuxCwd=${JSON.stringify(plan.linuxCwd)} windowsCwd=${JSON.stringify(plan.windowsCwd)}
|
|
829
|
+
termLog(`allocate: distro=${plan.distro} linuxCwd=${JSON.stringify(plan.linuxCwd)} windowsCwd=${JSON.stringify(plan.windowsCwd)} argv=${describeArgv(argv)} cols=${String(size.cols)} rows=${String(size.rows)} fifo=${fifo}`)
|
|
786
830
|
await requireBridgeRuntime(plan, select)
|
|
787
831
|
termLog('allocate: bridge runtime ok')
|
|
788
832
|
// The other generation's orphans, swept at the one moment this process can classify them:
|
|
@@ -943,10 +987,46 @@ export async function spawnWslTerminal({
|
|
|
943
987
|
termLog(`announce ok: pid=${String(announce.pid)} pgrp=${String(announce.pgrp)} fifo=${fifo}`)
|
|
944
988
|
|
|
945
989
|
control = local.spawn({
|
|
990
|
+
// The writer must NOT be able to outlive the reader it exists for.
|
|
991
|
+
//
|
|
992
|
+
// Measured defect (2026-10-24, twice on one host): when the bridge died, the
|
|
993
|
+
// data-done handler below called `control.terminate()`, which kills this
|
|
994
|
+
// wsl.exe CLIENT — but the writer runs inside the distribution and was
|
|
995
|
+
// reparented to /init, where its blocking `exec 3>"$1"` never returned. It
|
|
996
|
+
// then held the FIFO forever, and because every allocation mints a fresh
|
|
997
|
+
// random name no later session could reclaim it. The next terminal send
|
|
998
|
+
// waited for a readiness signal that never came and surfaced as
|
|
999
|
+
// `PTY send aborted before write`.
|
|
1000
|
+
//
|
|
1001
|
+
// A `bash` formulation CANNOT fix this, and that was measured rather than
|
|
1002
|
+
// assumed: `test -w` on a FIFO reports the permission BITS, not a live
|
|
1003
|
+
// reader, so a `[ -w ]` liveness loop passes immediately and then blocks
|
|
1004
|
+
// forever in `exec 3>"$fifo"` (measured: hung until an outer timeout at
|
|
1005
|
+
// 12004ms). Shell has no non-blocking open.
|
|
1006
|
+
//
|
|
1007
|
+
// So the open goes through python3, which IS already a hard prerequisite of
|
|
1008
|
+
// this path — requireBridgeRuntime refuses an allocation without it, so
|
|
1009
|
+
// using it here adds no new dependency. O_WRONLY|O_NONBLOCK turns "no reader
|
|
1010
|
+
// yet" and "no reader any more" into an ENXIO/CleanIO failure instead of an
|
|
1011
|
+
// indefinite block, which is exactly the property the orphan lacked.
|
|
1012
|
+
//
|
|
1013
|
+
// Measured on the shipped formulation: no reader -> exits in 15ms; live
|
|
1014
|
+
// reader -> both control lines delivered; reader vanishing mid-stream ->
|
|
1015
|
+
// exits within ~3.5s. The FIFO is also 0600 and created before the announce,
|
|
1016
|
+
// so a reader normally exists before this process starts.
|
|
946
1017
|
argv: buildWslExecArgv(plan, [
|
|
947
|
-
'
|
|
948
|
-
'
|
|
949
|
-
|
|
1018
|
+
'python3', '-c',
|
|
1019
|
+
'import os,sys\n'
|
|
1020
|
+
+ 'try:\n'
|
|
1021
|
+
+ ' fd=os.open(sys.argv[1],os.O_WRONLY|os.O_NONBLOCK)\n'
|
|
1022
|
+
+ 'except OSError:\n'
|
|
1023
|
+
+ ' sys.exit(0)\n'
|
|
1024
|
+
+ 'try:\n'
|
|
1025
|
+
+ ' for line in sys.stdin:\n'
|
|
1026
|
+
+ ' os.write(fd,line.encode())\n'
|
|
1027
|
+
+ 'finally:\n'
|
|
1028
|
+
+ ' os.close(fd)\n',
|
|
1029
|
+
fifo,
|
|
950
1030
|
], select),
|
|
951
1031
|
cwd: plan.windowsCwd,
|
|
952
1032
|
stdio: { stdin: 'pipe', stdout: 'ignore', stderr: 'pipe' },
|
package/lib/wsl/subprocess.js
CHANGED
|
@@ -29,7 +29,7 @@ import { SubprocessExecutableNotFoundError, SubprocessRuntime } from '@deepseek-
|
|
|
29
29
|
import z from '@deepseek-ai/schemastery'
|
|
30
30
|
import { requireLocalSubprocess } from './host-refs.js'
|
|
31
31
|
import { shellQuote } from './paths.js'
|
|
32
|
-
import { spawnWslTerminal, termLog } from './pty.js'
|
|
32
|
+
import { describeArgv, spawnWslTerminal, termLog } from './pty.js'
|
|
33
33
|
import { LoginShellUnresolvedError, WSL_CHILD_ENV, buildWslExecArgv, planWsl, resolveLoginShell, runWslShell, withWslEnvFlags } from './world.js'
|
|
34
34
|
|
|
35
35
|
/**
|
|
@@ -287,7 +287,7 @@ export class WslSubprocessRuntime extends SubprocessRuntime {
|
|
|
287
287
|
const terminalArgv = WINDOWS_SHELLS.has(base0)
|
|
288
288
|
? [await resolveLoginShell(plan.distro, this.config.username, { wslPath: this.config.wslPath })]
|
|
289
289
|
: spec.argv
|
|
290
|
-
termLog(`spawnTerminal: distro=${plan.distro} linuxCwd=${JSON.stringify(plan.linuxCwd)} terminalArgv=${
|
|
290
|
+
termLog(`spawnTerminal: distro=${plan.distro} linuxCwd=${JSON.stringify(plan.linuxCwd)} terminalArgv=${describeArgv(terminalArgv)} terminalType=${String(spec.terminalType)} username=${JSON.stringify(this.config.username ?? null)}`)
|
|
291
291
|
const handle = await spawnWslTerminal({
|
|
292
292
|
local: requireLocalSubprocess(),
|
|
293
293
|
plan,
|