dsh-wsl-tool 1.10.2 → 1.10.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/index.js CHANGED
@@ -64,26 +64,37 @@ if (Schema === null) {
64
64
  * documents the precedence: this configuration wins, the environment is the
65
65
  * deployment default, the built-in defaults are last.
66
66
  *
67
- * `distro` and `timeoutMs` are deliberately absent from the sidebar panel (they
68
- * are values, not features) but stay here so a patch or a panel could grow a
69
- * field for them without a schema change.
67
+ * `distro` and `timeoutMs` are values rather than switches, but they are here so a
68
+ * patch (or a future row in the panel) can set them without a schema change.
69
+ *
70
+ * EVERY field is `.volatile()`, and that is not decoration: DSH's settings service
71
+ * projects a plugin's config into a form only through `volatileForm()`, which keeps
72
+ * a field only when its nearest ancestor is marked volatile — a schema without one
73
+ * contributes no form at all, so the entry never reaches `ctx.settings.describe()`,
74
+ * its namespace is never served to the browser, and the sidebar panel waits for a
75
+ * scope that will never arrive. Volatile is also what the platform means by "can be
76
+ * edited without remounting", which is what the update hook below relies on.
77
+ *
78
+ * A volatile field must not sit inside another volatile one, so `tools` is a plain
79
+ * object whose leaf switches are volatile, not a volatile object.
70
80
  */
71
81
  export const Config = Schema?.object({
72
82
  tools: Schema.object({
73
- wsl: Schema.boolean().default(true).description('注册 `wsl` 工具:在 WSL 里执行 Linux 命令。'),
74
- path: Schema.boolean().default(true).description('注册 `wsl-path` 工具:Windows 路径与 /mnt/... 互转。'),
75
- env: Schema.boolean().default(true).description('注册 `wsl-env` 工具:汇总 WSL 环境能力。'),
76
- }).description('要注册哪几个工具。'),
77
- backgroundJobs: Schema.boolean().default(true).description('允许 `runInBackground`,由内置 job 工具读回结果。'),
78
- translatePaths: Schema.boolean().default(true).description('默认把命令里的 Windows 路径转成 /mnt/...。'),
79
- startInSessionWorkspace: Schema.boolean().default(false).description('未传 `workdir` 时从会话工作区开始,而不是 Linux 家目录。'),
80
- dangerGuard: Schema.boolean().default(true).description('危险命令必须显式 `allowDangerous` 才放行。关掉后模型可直接删除/分区。'),
81
- distro: Schema.string().default('').description('要固定使用的发行版;留空则用系统默认(也可用 DSH_WSL_DISTRO)。'),
82
- timeoutMs: Schema.number().default(0).description('默认命令超时毫秒数;0 表示用内置默认(也可用 DSH_WSL_TIMEOUT_MS)。'),
83
+ wsl: Schema.boolean().default(true).description('注册 `wsl` 工具:在 WSL 里执行 Linux 命令。').volatile(),
84
+ path: Schema.boolean().default(true).description('注册 `wsl-path` 工具:Windows 路径与 /mnt/... 互转。').volatile(),
85
+ env: Schema.boolean().default(true).description('注册 `wsl-env` 工具:汇总 WSL 环境能力。').volatile(),
86
+ }).description('要注册哪几个工具;这三个开关在下次启动 DSH 后生效。'),
87
+ backgroundJobs: Schema.boolean().default(true).description('允许 `runInBackground`,由内置 job 工具读回结果。').volatile(),
88
+ translatePaths: Schema.boolean().default(true).description('默认把命令里的 Windows 路径转成 /mnt/...。').volatile(),
89
+ startInSessionWorkspace: Schema.boolean().default(false).description('未传 `workdir` 时从会话工作区开始,而不是 Linux 家目录。').volatile(),
90
+ dangerGuard: Schema.boolean().default(true).description('危险命令必须显式 `allowDangerous` 才放行。关掉后模型可直接删除/分区。').volatile(),
91
+ distro: Schema.string().default('').description('要固定使用的发行版;留空则用系统默认(也可用 DSH_WSL_DISTRO)。').volatile(),
92
+ timeoutMs: Schema.number().default(0).description('默认命令超时毫秒数;0 表示用内置默认(也可用 DSH_WSL_TIMEOUT_MS)。').volatile(),
83
93
  }).description('dsh-wsl 的功能开关与默认值。')
84
94
 
85
95
  export function apply(ctx, settings = {}) {
86
- // Resolved once per mount: a settings change is a restart, not a live edit.
96
+ // Resolved once per mount. Behaviour reads this object at call time, so the
97
+ // volatile-update hook below can change it in place without remounting.
87
98
  const config = resolveConfig(process.env, settings)
88
99
  const runner = createRunner(ctx, config)
89
100
  // `ctx` is passed to the `wsl` tool for the optional `jobs` service only
@@ -92,4 +103,20 @@ export function apply(ctx, settings = {}) {
92
103
  if (config.tools.wsl) ctx.tools.register(createWslTool({ ctx, config, runner }))
93
104
  if (config.tools.path) ctx.tools.register(createWslPathTool({ config, runner }))
94
105
  if (config.tools.env) ctx.tools.register(createWslEnvTool({ config, runner }))
106
+
107
+ // A volatile edit is written into the same object the loader handed us and then
108
+ // announced, so re-resolving in place is what makes the behaviour switches
109
+ // (background jobs, path translation, the guard, workdir, distro, timeout) take
110
+ // effect without a restart. The three `tools.*` switches are the exception: they
111
+ // decide which tools were registered, so they need the next start, and the panel
112
+ // says so on those rows.
113
+ if (typeof ctx.on === 'function') {
114
+ ctx.on('loader/volatile-update', () => {
115
+ try {
116
+ Object.assign(config, resolveConfig(process.env, settings))
117
+ } catch (error) {
118
+ console.error('dsh-wsl-tool: applying a live settings change failed', error)
119
+ }
120
+ })
121
+ }
95
122
  }
package/lib/client.js CHANGED
@@ -191,7 +191,12 @@ window.__ModuleLoader__.load({
191
191
  if (cursor === null || typeof cursor !== 'object') return undefined
192
192
  cursor = cursor[step]
193
193
  }
194
- return cursor
194
+ // A `.volatile()` field can arrive as a cosmokit cell rather than its value;
195
+ // the platform's own form model hands out plain values, but unwrapping here
196
+ // costs nothing and keeps a switch from rendering a cell object as "on".
197
+ return cursor !== null && typeof cursor === 'object' && typeof cursor.get === 'function'
198
+ ? cursor.get()
199
+ : cursor
195
200
  }
196
201
 
197
202
  /**
@@ -554,7 +559,7 @@ window.__ModuleLoader__.load({
554
559
  { key: 'header', style: HEADER_STYLE },
555
560
  React.createElement('h2', { style: TITLE_STYLE }, 'WSL'),
556
561
  React.createElement('p', { style: DESCRIPTION_STYLE }, '通过 WSL 在 Windows 上执行 Linux 命令的开关与说明'),
557
- React.createElement('p', { style: NOTE_STYLE }, '改动会在下次启动 DSH 后生效'),
562
+ React.createElement('p', { style: NOTE_STYLE }, '工具开关在下次启动 DSH 后生效;其余开关即时生效。'),
558
563
  ),
559
564
  )
560
565
 
package/lib/config.js CHANGED
@@ -92,7 +92,25 @@ function envInt(env, name, fallback, min, max) {
92
92
 
93
93
  /** A configured boolean, or the fallback when the layer did not set one. */
94
94
  function setting(value, fallback) {
95
- return typeof value === 'boolean' ? value : fallback
95
+ const raw = unbox(value)
96
+ return typeof raw === 'boolean' ? raw : fallback
97
+ }
98
+
99
+ /**
100
+ * Read one configuration value, unwrapping a volatile cell.
101
+ *
102
+ * A `.volatile()` schema field does NOT resolve to its value: schemastery wraps it
103
+ * with cosmokit's `createVolatile(default)`, which is why the shipped plugins read
104
+ * theirs as `config.timeoutMs.get()` (`dsh-bash-local`). The cell is written in
105
+ * place when the user edits the field, and that in-place write is exactly what makes
106
+ * an edit apply without remounting the plugin — so reading through `.get()` is both
107
+ * required for correctness and how live updates arrive. A plain value (an older
108
+ * host, or a hand-built settings object in a test) passes through untouched.
109
+ */
110
+ function unbox(value) {
111
+ return value !== null && typeof value === 'object' && typeof value.get === 'function'
112
+ ? value.get()
113
+ : value
96
114
  }
97
115
 
98
116
  /**
@@ -101,7 +119,8 @@ function setting(value, fallback) {
101
119
  * @returns a distribution name to pin, or null to use the system default.
102
120
  */
103
121
  function resolveDistro(env, configured) {
104
- if (typeof configured === 'string' && configured.trim() !== '') return configured.trim()
122
+ const distro = unbox(configured)
123
+ if (typeof distro === 'string' && distro.trim() !== '') return distro.trim()
105
124
  const raw = env.DSH_WSL_DISTRO
106
125
  return typeof raw === 'string' && raw.trim() !== '' ? raw.trim() : null
107
126
  }
@@ -121,7 +140,7 @@ function resolveWorkdir(env, settings) {
121
140
  // Only the ON position is authoritative: off means "whatever the layers below
122
141
  // say", so `DSH_WSL_WORKDIR=session` keeps working for a user who never opened
123
142
  // the panel, and a fresh install still starts in `~`.
124
- if (settings.startInSessionWorkspace === true) return null
143
+ if (unbox(settings.startInSessionWorkspace) === true) return null
125
144
  const raw = env.DSH_WSL_WORKDIR
126
145
  if (typeof raw !== 'string' || raw.trim() === '') return DEFAULTS.workdir
127
146
  const value = raw.trim()
@@ -148,11 +167,12 @@ export function resolveConfig(env = process.env, settings = {}) {
148
167
  const maxCommandTimeoutMs = envInt(
149
168
  env, 'DSH_WSL_MAX_TIMEOUT_MS', DEFAULTS.maxCommandTimeoutMs, TIMEOUT_MS_MIN, DEFAULTS.maxTimerDelayMs,
150
169
  )
151
- const tools = settings.tools ?? {}
170
+ const tools = unbox(settings.tools) ?? {}
152
171
  // 0 means "not configured", so the environment and the built-in default still
153
172
  // decide; a positive value is the user's own deadline.
154
- const configuredTimeoutMs = Number.isInteger(settings.timeoutMs) && settings.timeoutMs > 0
155
- ? settings.timeoutMs
173
+ const configuredTimeout = unbox(settings.timeoutMs)
174
+ const configuredTimeoutMs = Number.isInteger(configuredTimeout) && configuredTimeout > 0
175
+ ? configuredTimeout
156
176
  : undefined
157
177
  return {
158
178
  ...DEFAULTS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-wsl-tool",
3
- "version": "1.10.2",
3
+ "version": "1.10.4",
4
4
  "description": "Run Linux commands from Windows through WSL, essentially matching a native Linux DSH for command execution: background jobs, stdin, path translation, a destructive-command guard and a WSL capability report. A left-sidebar panel switches each capability on or off, each with a one-line explanation, and an opt-in patch gives the desktop app's sidebar terminal a WSL shell. WSL calls run below the DSH sandbox, and a project on a Windows drive keeps Windows filesystem semantics.",
5
5
  "type": "module",
6
6
  "main": "index.js",