dsh-gitbash-shell 0.34.1 → 0.34.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/CHANGELOG.md CHANGED
@@ -3,6 +3,17 @@
3
3
  > 倒序排列,新版本条目在最上面。条目格式:`## vX.Y.Z — YYYY-MM-DD` + 类型(feat / fix / docs / chore)+ 要点 + 相关链接。
4
4
  > 纪律见 AGENTS.md「变更记录纪律」:发版前先更新本文件并随版本提交;事故复盘、复现与真机验证记录也记在这里。
5
5
 
6
+ ## v0.34.2 — 2026-10-09
7
+
8
+ **类型**:fix([issue #15](https://github.com/KannaKuron/dsh-gitbash-shell/issues/15):极简模式 · Git Bash 的 PTY 终端无法启动,node-pty `File not found`)
9
+
10
+ - **根因**:`minimalPluginsFor()` 是四个变体里**唯一**给 terminal-bash 行写死 `shellPath: DEFAULT_GIT_BASH`(`C:/Program Files/Git/bin/bash.exe`)的地方——Git 装在自定义路径(Scoop / 便携版 / 其他盘符)的 Windows 机器上,PTY spawn 在进程创建阶段即失败,且本插件撤掉 `pwsh-sandbox` 而 dsh 每进程只允许一个 `ctx.shell`,极简模式 shell 能力完全归零、无备用。其余三个变体(`pluginsFor`)不含 `shellPath`、走 `src/shell.js` → `resolveGitBashCached()` 动态解析,天然正确——与 issue #11 同类残留,这次漏在 PTY 行上。
11
+ - **修法(采纳报告人 @Jingqi05 的「入参传入」变体,`compositions.js` 保持纯数据)**:`minimalPluginsFor({ bashPath })`——win32 下有解析结果用解析结果,否则兜底 `DEFAULT_GIT_BASH`(失败保持可见的 spawn 报错、绝不换成别的 shell,§4h 两条硬边界不变);非 win 恒 `/bin/bash`。
12
+ - **解析来源比报告人裸 `resolveGitBashCached({})` 更完整一层**:`src/index.js` 新增 `minimalPtyBashPath(readBashResolution)`,每次(重)注册时懒读(issue #13 纪律:不在 apply() 期冻结),喂的是**含显式配置层**的 `readConfiguredBashPath` 链(`gitbash-executor` 行 > `gitbash-shell` 行 `bashPath` > 自动链)——与执行器同一条解析链,设置里显式填的路径优先。
13
+ - **测试**:冒烟 +1 用例(win32 平台 stub 三态:解析成功用解析结果 / 失败与空值兜底默认 / 非 win `/bin/bash` + 读取器异常安全 + 接线断言);102 项全绿(基线 101:100 pass + 1 win32-only skip → 101 pass + 同一 skip,纯增量无回归);同步更新 3 处锁源码文本的既有断言(`registerVariant` / `runDeclarativeEra` 调用行、`pluginsFor` 形参)。
14
+ - **真机**:Windows 真机复现与修复 A/B 对照(真实 node-pty:修复前 spawn `File not found: C:/Program Files/Git/bin/bash.exe`,修复后 `D:/git/Git/bin/bash.exe` exit=0)由报告人 @Jingqi05 在 issue 中完成,修复评论已引用;本机(macOS)为逻辑级验证 + 全量测试。
15
+ - **已知边界(已在 issue 评论如实说明)**:极简变体行集是注册时快照,运行中改 `bashPath` 设置需重启 dsh 才进 PTY 行(其余三变体走执行器懒解析、无此限制);热重注册需跟踪 `registeredBashPath` 的 retire/重建模式,超出本次范围。
16
+
6
17
  ## v0.34.1 — 2026-10-05
7
18
 
8
19
  **类型**:chore(清理 `dsh.client.inject` 里的死引用 `@deepseek-ai/dsh-client-runtime`)
package/dsh.plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "dsh-external/dsh-gitbash-shell",
3
- "version": "0.34.1",
3
+ "version": "0.34.2",
4
4
  "main": "./src/index.js",
5
5
  "description": "Windows 全模式 Git Bash:用 Git for Windows bash 替换 PowerShell 执行器,声明式注册 standard/minimal/code/cordis 的 Git Bash 变体 preset(preset id 永不变)。联动:dsh-ptc-cordis-preset(两者同装时 PTC 创造模式带 Git Bash 行且可去重)+ dsh-better-sidebar(终端 UI 标签与模型 terminal_* 工具自动切到 Git Bash)。",
6
6
  "engines": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-gitbash-shell",
3
- "version": "0.34.1",
3
+ "version": "0.34.2",
4
4
  "description": "Windows 上让 agent 的每条 shell 命令改经 Git for Windows bash 执行;物化四个内置预设的 Git Bash 变体,统一 MSYS 盘根路径方言,侧栏终端接管可开关。· Run every agent shell command through Git for Windows bash on Windows; materialize Git Bash variants of the built-in presets, unify the MSYS drive-root path dialect, and adopt the sidebar terminal behind a switch.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -225,9 +225,17 @@ export function pluginsFor({ kind, gitBash, skillsDir, pythonActive = false, hos
225
225
  * The minimal variant: the official single-tool preset with the Git Bash
226
226
  * delta — bash rows always on (the terminal row pins the Git Bash binary on
227
227
  * Windows), pwsh rows always off (pure).
228
+ * @param {object} [input]
229
+ * @param {string|undefined} [input.bashPath] - the RESOLVED Git for Windows
230
+ * bash (the ONE chain of src/bash-path.js, explicit tier first — the same
231
+ * resolver the executor runs; wired in src/index.js and read lazily at
232
+ * each registration). `undefined` when the chain failed: the row then
233
+ * pins the historical default, so a missing Git stays a VISIBLE spawn
234
+ * failure — never a substitute shell (issue #15: the hardcoded default
235
+ * broke every PTY spawn where Git lives outside `C:/Program Files/Git`).
228
236
  * @returns {object[]} the declarative plugins list.
229
237
  */
230
- export function minimalPluginsFor() {
238
+ export function minimalPluginsFor({ bashPath } = {}) {
231
239
  const win = typeof process !== 'undefined' && process.platform === 'win32'
232
240
  return [
233
241
  {
@@ -252,7 +260,7 @@ export function minimalPluginsFor() {
252
260
  disabled: false,
253
261
  config: {
254
262
  timeoutMs: 300000,
255
- shellPath: win ? DEFAULT_GIT_BASH : '/bin/bash',
263
+ shellPath: win ? (typeof bashPath === 'string' && bashPath !== '' ? bashPath : DEFAULT_GIT_BASH) : '/bin/bash',
256
264
  },
257
265
  },
258
266
  {
package/src/index.js CHANGED
@@ -1674,16 +1674,42 @@ function safeProbe(ok, specifier) {
1674
1674
  }
1675
1675
  }
1676
1676
 
1677
+ /**
1678
+ * The minimal variant's PTY row (`terminal-bash`) pins the shell binary it
1679
+ * spawns; before issue #15 that was the one hardcoded `DEFAULT_GIT_BASH`
1680
+ * copy left in the tree while every other consumer had moved to the
1681
+ * resolver, so a Git installed outside `C:/Program Files/Git` failed every
1682
+ * PTY spawn with node-pty's "File not found". The row now rides the ONE
1683
+ * resolution chain (explicit config tier first — readConfiguredBashPath,
1684
+ * the same chain the executor runs), read LAZILY per registration
1685
+ * (issue #13: never frozen at apply time). An unresolved chain returns
1686
+ * `undefined` and the composition then pins the historical default: per the
1687
+ * two hard rules a missing Git Bash stays a VISIBLE spawn failure — never a
1688
+ * substitute shell.
1689
+ * @param {() => object} [readResolution] - the lazy resolution reader shared
1690
+ * with the executor and the capability service.
1691
+ * @returns {string|undefined} the resolved Git Bash path, or `undefined`
1692
+ * when unresolved (the composition falls back to the visible default).
1693
+ */
1694
+ export function minimalPtyBashPath(readResolution) {
1695
+ try {
1696
+ const resolution = readResolution()
1697
+ return resolution && resolution.ok && resolution.path ? resolution.path : undefined
1698
+ } catch {
1699
+ return undefined
1700
+ }
1701
+ }
1702
+
1677
1703
  /**
1678
1704
  * Register one variant definition; returns the unregister function. Mount
1679
1705
  * failures stay visible through the roster's broken diagnostic instead of
1680
1706
  * failing the boot.
1681
1707
  */
1682
- async function registerVariant(ctx, presetId, { gitBashActive, skillsDir, pythonActive, hostExtras = {} }) {
1708
+ async function registerVariant(ctx, presetId, { gitBashActive, skillsDir, pythonActive, hostExtras = {}, readBashResolution }) {
1683
1709
  const meta = PRESET_META[presetId]
1684
1710
  if (!meta) return undefined
1685
1711
  const plugins = meta.kind === 'minimal'
1686
- ? minimalPluginsFor()
1712
+ ? minimalPluginsFor({ bashPath: minimalPtyBashPath(readBashResolution) })
1687
1713
  : pluginsFor({ kind: meta.kind, gitBash: gitBashActive, skillsDir, pythonActive, hostExtras })
1688
1714
  return ctx.agentPresets.register({
1689
1715
  id: presetId,
@@ -1711,9 +1737,12 @@ async function registerVariant(ctx, presetId, { gitBashActive, skillsDir, python
1711
1737
  * @param {object} ctx - the plugin's mounting context.
1712
1738
  * @param {string[]} presetIds - the row's configured variants.
1713
1739
  * @param {() => boolean} readSuppress - live dedupe switch reader.
1740
+ * @param {() => object} readBashResolution - lazy Git Bash resolution reader
1741
+ * (the chain shared with the executor and the capability); the minimal
1742
+ * variant's PTY row reads it once per registration (issue #15).
1714
1743
  * @returns {Promise<void>} resolves once the first reconcile finished.
1715
1744
  */
1716
- async function runDeclarativeEra(ctx, presetIds, readSuppress) {
1745
+ async function runDeclarativeEra(ctx, presetIds, readSuppress, readBashResolution) {
1717
1746
  cleanupLegacyTrees(presetIds)
1718
1747
  const gitBashActive = process.platform === 'win32'
1719
1748
  const skillsDir = await resolveSkillsDir()
@@ -1765,7 +1794,7 @@ async function runDeclarativeEra(ctx, presetIds, readSuppress) {
1765
1794
  for (const presetId of want) {
1766
1795
  if (live.has(presetId)) continue
1767
1796
  try {
1768
- const unregister = await registerVariant(ctx, presetId, { gitBashActive, skillsDir, pythonActive: peerPython, hostExtras })
1797
+ const unregister = await registerVariant(ctx, presetId, { gitBashActive, skillsDir, pythonActive: peerPython, hostExtras, readBashResolution })
1769
1798
  if (unregister) {
1770
1799
  live.set(presetId, unregister)
1771
1800
  console.log(TAG + " preset '" + presetId + "' registered declaratively")
@@ -2486,7 +2515,7 @@ export async function apply(ctx, config = {}) {
2486
2515
  // more), so this branch serves the variants and returns.
2487
2516
  if (ctx.agentPresets && typeof ctx.agentPresets.register === 'function') {
2488
2517
  try {
2489
- await runDeclarativeEra(ctx, presetIds, () => liveSettings.suppressPeerCordis())
2518
+ await runDeclarativeEra(ctx, presetIds, () => liveSettings.suppressPeerCordis(), readBashResolution)
2490
2519
  } catch (error) {
2491
2520
  console.log(TAG + ' declarative registration failed: ' + (error && error.message ? error.message : error))
2492
2521
  }