dsh-gitbash-shell 0.34.1 → 0.34.3

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,29 @@
3
3
  > 倒序排列,新版本条目在最上面。条目格式:`## vX.Y.Z — YYYY-MM-DD` + 类型(feat / fix / docs / chore)+ 要点 + 相关链接。
4
4
  > 纪律见 AGENTS.md「变更记录纪律」:发版前先更新本文件并随版本提交;事故复盘、复现与真机验证记录也记在这里。
5
5
 
6
+ ## v0.34.3 — 2026-10-11
7
+
8
+ **类型**:fix([issue #16](https://github.com/KannaKuron/dsh-gitbash-shell/issues/16):Git Bash 预设 persona suffix 引用 `{{cwd}}`,dsh 0.2.1-alpha.1+ 每轮在任何模型请求前抛错)+ dsh 0.2.1-alpha.2 官方组合对齐。
9
+
10
+ - **根因(issue #16,报告 @tkhs101)**:`compositions.js` 的 persona 行带 `suffix: 'Your working directory is {{cwd}}.'`——上游 **79bd3d8da7**(2026-09-13,session working-directory 集中化)删除了 agent-loop 的 `cwd` 提示词变量注册,而 system-prompt 对未注册变量引用**在任何模型请求之前直接抛错**,整轮失败。0.1.7-rc.2 仍注册该变量所以 desktop 侧无感;0.2.1-alpha.1 起必炸。官方预设同步在 **0.2.1-alpha.2** 删除该子句(**2eb058d887**,"remove retired directory persona templates")。修法即上游 README 明文指令:**从四个变体删除该子句**——工作目录由宿主 working-directory 运行时上下文(WORKING_DIRECTORY,order 100)提供,官方各预设均无 persona suffix。冒烟新增回归锁:组合不得再引用 `{{cwd}}`。
11
+ - **组合对齐(0.2.1-alpha.2 官方漂移,`packages/bundle/web-app/presets/`)**:
12
+ - 官方删除的三个占位行(`tool-subagent-codex` / `tool-subagent-claude-code` / `tool-ralph`,上游 **8ed0b530ed**,codex/claude-code provider 移交「按需安装」官方组合包)从全部变体删除——三行在各代宿主都是 `disabled: true` 占位,删除零损失;冒烟断言 ralph 不复存在。
13
+ - `tool-subagent` / `tool-subagent-fork` 的 `backgroundMode: 'continuable'` 改 **hostExtras 双态**(0.2.1-alpha.2 官方删除该字段;≤0.2.0 官方带)。既有 `probeHostExtras` 信号直接复用,零新增探测。
14
+ - plan-mode section **首句双态**(共享正文):0.2.1-alpha.2 官方把首句改回「until the user approves your plan through exit_plan_mode」(alpha.1 为「until exit_plan_mode succeeds」),跟随同一条 hostExtras 信号;其余段落逐字节不变。
15
+ - **`tests/align-official.mjs`**:PRESET_SHA256 四 pin 重新钉到 0.2.1-alpha.2(standard/cordis/ptc 变更,minimal 逐字节不变);`DSH_HARNESS_SRC` 指向 0.2.1-alpha.2 checkout 全 OK。
16
+ - 测试:smoke 101 过;唯一失败 `minimal PTY shellPath … (issue #15)` 为本机 Git Bash 环境下的既有失败(与 0.34.2 基线一致,stash 验证与本版改动无关,待单独排查)。
17
+
18
+ ## v0.34.2 — 2026-10-09
19
+
20
+ **类型**:fix([issue #15](https://github.com/KannaKuron/dsh-gitbash-shell/issues/15):极简模式 · Git Bash 的 PTY 终端无法启动,node-pty `File not found`)
21
+
22
+ - **根因**:`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 行上。
23
+ - **修法(采纳报告人 @Jingqi05 的「入参传入」变体,`compositions.js` 保持纯数据)**:`minimalPluginsFor({ bashPath })`——win32 下有解析结果用解析结果,否则兜底 `DEFAULT_GIT_BASH`(失败保持可见的 spawn 报错、绝不换成别的 shell,§4h 两条硬边界不变);非 win 恒 `/bin/bash`。
24
+ - **解析来源比报告人裸 `resolveGitBashCached({})` 更完整一层**:`src/index.js` 新增 `minimalPtyBashPath(readBashResolution)`,每次(重)注册时懒读(issue #13 纪律:不在 apply() 期冻结),喂的是**含显式配置层**的 `readConfiguredBashPath` 链(`gitbash-executor` 行 > `gitbash-shell` 行 `bashPath` > 自动链)——与执行器同一条解析链,设置里显式填的路径优先。
25
+ - **测试**:冒烟 +1 用例(win32 平台 stub 三态:解析成功用解析结果 / 失败与空值兜底默认 / 非 win `/bin/bash` + 读取器异常安全 + 接线断言);102 项全绿(基线 101:100 pass + 1 win32-only skip → 101 pass + 同一 skip,纯增量无回归);同步更新 3 处锁源码文本的既有断言(`registerVariant` / `runDeclarativeEra` 调用行、`pluginsFor` 形参)。
26
+ - **真机**: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)为逻辑级验证 + 全量测试。
27
+ - **已知边界(已在 issue 评论如实说明)**:极简变体行集是注册时快照,运行中改 `bashPath` 设置需重启 dsh 才进 PTY 行(其余三变体走执行器懒解析、无此限制);热重注册需跟踪 `registeredBashPath` 的 retire/重建模式,超出本次范围。
28
+
6
29
  ## v0.34.1 — 2026-10-05
7
30
 
8
31
  **类型**: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.3",
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.3",
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": {
@@ -27,7 +27,13 @@ function off(value) {
27
27
  return value === true ? { disabled: true } : {}
28
28
  }
29
29
 
30
- const PLAN_SECTION = `You are in plan mode. Stay in plan mode until exit_plan_mode succeeds or the user switches the session mode. Imperative language to implement changes means plan the implementation, not execute it. A user's conversational agreement — including an answer confirming something you asked — approves nothing and does not end plan mode; fold the confirmed decision into the plan and submit it through exit_plan_mode.
30
+ // Plan-mode section, shared body with an era-specific FIRST SENTENCE (each
31
+ // byte-exact from its era's official patch.yml): dsh 0.1.7..0.2.1-alpha.1
32
+ // say "until exit_plan_mode succeeds", 0.2.1-alpha.2 rephrased to "until the
33
+ // user approves your plan through exit_plan_mode". The hostExtras probe
34
+ // already splits the eras for the row set, so the wording rides the same
35
+ // signal — no new probe, and each generation mirrors its own official text.
36
+ const PLAN_SECTION_TAIL = `Imperative language to implement changes means plan the implementation, not execute it. A user's conversational agreement — including an answer confirming something you asked — approves nothing and does not end plan mode; fold the confirmed decision into the plan and submit it through exit_plan_mode.
31
37
 
32
38
  Explore first. Use non-mutating reads, searches, static analysis, and checks to ground the plan in the actual repository. Do not edit or write files, change configuration, run formatters or code generation that rewrites tracked files, commit, or otherwise carry out the plan. Prefer existing functions and patterns over new machinery.
33
39
 
@@ -40,6 +46,10 @@ Make the plan decision-complete: state the goal and success criteria; group impl
40
46
  When ready, call exit_plan_mode with the complete plan markdown, starting with a # title. Make exit_plan_mode the only and final tool call in that assistant response: it presents the plan for approval, and implementation begins only in a later step after approval. Do not paste the final plan as a plain reply or ask "should I proceed?" through prose or ask_user_question. If review rejects it, incorporate the feedback and present again. If the review channel is unavailable or aborted, stay in plan mode and ask the user to switch modes manually; do not proceed with implementation.
41
47
  `
42
48
 
49
+ const planSectionFor = (hostEra021) => hostEra021
50
+ ? `You are in plan mode. Stay in plan mode until the user approves your plan through exit_plan_mode or switches the session mode. ${PLAN_SECTION_TAIL}`
51
+ : `You are in plan mode. Stay in plan mode until exit_plan_mode succeeds or the user switches the session mode. ${PLAN_SECTION_TAIL}`
52
+
43
53
  /**
44
54
  * The shared full-tool rows for standard / ptc / cordis variants (pure).
45
55
  * @param {object} input
@@ -86,7 +96,13 @@ export function pluginsFor({ kind, gitBash, skillsDir, pythonActive = false, hos
86
96
  name: '@deepseek-ai/dsh-persona',
87
97
  config: {
88
98
  prefix: 'You are a coding agent powered by the {{model}} model.',
89
- suffix: 'Your working directory is {{cwd}}.',
99
+ // NO suffix: the host dropped the `cwd` prompt variable on 2026-09-13
100
+ // (upstream 79bd3d8da7 — working directories centralized into the
101
+ // working-directory service) and removed the persona suffix clause
102
+ // from every official preset in 0.2.1-alpha.2 (upstream 2eb058d887).
103
+ // An unregistered variable reference throws BEFORE any model request
104
+ // and kills the whole turn (issue #16, dsh 0.2.1-alpha.1+). The
105
+ // host's working-directory runtime context supplies the directory.
90
106
  },
91
107
  },
92
108
  {
@@ -120,7 +136,7 @@ export function pluginsFor({ kind, gitBash, skillsDir, pythonActive = false, hos
120
136
  {
121
137
  id: 'plan-mode',
122
138
  name: '@deepseek-ai/dsh-plan-mode',
123
- config: { section: PLAN_SECTION },
139
+ config: { section: planSectionFor(timeContext) },
124
140
  },
125
141
  ],
126
142
  },
@@ -150,24 +166,12 @@ export function pluginsFor({ kind, gitBash, skillsDir, pythonActive = false, hos
150
166
  {
151
167
  id: 'tool-subagent',
152
168
  name: '@deepseek-ai/dsh-tool-subagent',
153
- config: { provider: 'spawn', toolName: 'subagent', modelSelectionSettings: true, backgroundMode: 'continuable', ...subagentDeny },
169
+ config: { provider: 'spawn', toolName: 'subagent', modelSelectionSettings: true, ...(timeContext ? {} : { backgroundMode: 'continuable' }), ...subagentDeny },
154
170
  },
155
171
  {
156
172
  id: 'tool-subagent-fork',
157
173
  name: '@deepseek-ai/dsh-tool-subagent',
158
- config: { provider: 'fork', toolName: 'subagent_fork', backgroundMode: 'continuable', ...subagentDeny },
159
- },
160
- {
161
- id: 'tool-subagent-codex',
162
- name: '@deepseek-ai/dsh-tool-subagent',
163
- disabled: true,
164
- config: { provider: 'codex', toolName: 'subagent_codex', backgroundMode: 'one-shot', maxDepth: 'provider-managed' },
165
- },
166
- {
167
- id: 'tool-subagent-claude-code',
168
- name: '@deepseek-ai/dsh-tool-subagent',
169
- disabled: true,
170
- config: { provider: 'claude-code', toolName: 'subagent_claude_code', backgroundMode: 'one-shot', maxDepth: 'provider-managed' },
174
+ config: { provider: 'fork', toolName: 'subagent_fork', ...(timeContext ? {} : { backgroundMode: 'continuable' }), ...subagentDeny },
171
175
  },
172
176
  {
173
177
  id: 'workflow-ptc',
@@ -176,12 +180,12 @@ export function pluginsFor({ kind, gitBash, skillsDir, pythonActive = false, hos
176
180
  config: { provider: 'spawn' },
177
181
  },
178
182
  { id: 'tool-workflow', name: '@deepseek-ai/dsh-tool-workflow', ...off(!workflowOn) },
179
- {
180
- id: 'tool-ralph',
181
- name: '@deepseek-ai/dsh-tool-ralph',
182
- disabled: true,
183
- config: { subagentProvider: 'spawn', maxRounds: 64 },
184
- },
183
+ // NO tool-subagent-codex / tool-subagent-claude-code / tool-ralph:
184
+ // the official presets retired these three disabled placeholder rows
185
+ // in 0.2.1-alpha.2 (upstream 8ed0b530ed — the codex/claude-code
186
+ // provider rows moved to on-demand official plugin bundles). They
187
+ // were `disabled: true` on every host generation, so dropping them
188
+ // loses nothing and re-aligns with the shipped 0.2.1 row split.
185
189
  ],
186
190
  },
187
191
  { id: 'tool-ask-user', name: '@deepseek-ai/dsh-tool-ask-user' },
@@ -225,9 +229,17 @@ export function pluginsFor({ kind, gitBash, skillsDir, pythonActive = false, hos
225
229
  * The minimal variant: the official single-tool preset with the Git Bash
226
230
  * delta — bash rows always on (the terminal row pins the Git Bash binary on
227
231
  * Windows), pwsh rows always off (pure).
232
+ * @param {object} [input]
233
+ * @param {string|undefined} [input.bashPath] - the RESOLVED Git for Windows
234
+ * bash (the ONE chain of src/bash-path.js, explicit tier first — the same
235
+ * resolver the executor runs; wired in src/index.js and read lazily at
236
+ * each registration). `undefined` when the chain failed: the row then
237
+ * pins the historical default, so a missing Git stays a VISIBLE spawn
238
+ * failure — never a substitute shell (issue #15: the hardcoded default
239
+ * broke every PTY spawn where Git lives outside `C:/Program Files/Git`).
228
240
  * @returns {object[]} the declarative plugins list.
229
241
  */
230
- export function minimalPluginsFor() {
242
+ export function minimalPluginsFor({ bashPath } = {}) {
231
243
  const win = typeof process !== 'undefined' && process.platform === 'win32'
232
244
  return [
233
245
  {
@@ -252,7 +264,7 @@ export function minimalPluginsFor() {
252
264
  disabled: false,
253
265
  config: {
254
266
  timeoutMs: 300000,
255
- shellPath: win ? DEFAULT_GIT_BASH : '/bin/bash',
267
+ shellPath: win ? (typeof bashPath === 'string' && bashPath !== '' ? bashPath : DEFAULT_GIT_BASH) : '/bin/bash',
256
268
  },
257
269
  },
258
270
  {
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
  }