dsh-gitbash-shell 0.35.0 → 0.35.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,23 @@
3
3
  > 倒序排列,新版本条目在最上面。条目格式:`## vX.Y.Z — YYYY-MM-DD` + 类型(feat / fix / docs / chore)+ 要点 + 相关链接。
4
4
  > 纪律见 AGENTS.md「变更记录纪律」:发版前先更新本文件并随版本提交;事故复盘、复现与真机验证记录也记在这里。
5
5
 
6
+ ## v0.35.2 — 2026-10-12
7
+
8
+ **类型**:fix(Windows 真机报告;行内补位的时序洞 + 调用层兜底)。
9
+
10
+ - **修复:已存在的 agent 拿不到补位(用户 Windows 真机报告)**。症状:AI 调 `pwsh` 工具收到 `/usr/bin/bash: line 1: Select-String: command not found`——假 pwsh 的执行器在本机就是 `ctx.shell`(Git Bash),PowerShell 命令进了 bash。根因:补位判定此前**只挂在 `agent/created`** 上,本插件行**激活晚于 agent 存在**时(插件热重载、宿主先恢复会话后本 bundle 行才挂完),全部已存在的 agent 保持假 pwsh 且没有 bash,且永不再触发判定。修复为 **dsh-ops 验证过的双重独立层**(`installHostShellDenial` 同款形态):
11
+ - **层 1 — 挂载时补扫**:`mountInlineTools` 新增 `agents(ctx)` 依赖(index.js 传入 `ctx.get('agents')`),挂载那一刻对 `agents.list()` 里的每个**已存活 agent** 跑一遍 `reconcile`——与 created 事件同一条路径,250ms×5 有界重试等同样生效;
12
+ - **层 2 — 调用层守卫**:新增 `tools/pre-execute` 监听,凡 `pwsh` 调用到达分发层一律 deny,理由句直接指路替换品(bash 工具 / `gitbash_pwsh`)。两层层层独立:即便 per-agent 的 `restrict({ deny })` 输了竞态,谎工具也到不了执行器。
13
+ - **顺手修复:重名声明 SyntaxError**。v0.35.2 开发中引入的 `agents` 依赖与 `mountInlineTools` 内部的 agent 状态 Map 同名(`const` 重复声明,模块整体加载失败)。内部 Map 改名 `tracked`;冒烟加防回归断言(源码不得再现 `const agents = new Map()`)。
14
+ - **冒烟**:新增「inline tools: the mount-time sweep and the call-layer guard exist (v0.35.2)」;「…wires the patch…」补 `agents` 依赖接线断言。合计 114 项(113 过 + 1 跳)。
15
+ - **Windows 真机复验清单(升级后)**:①官方模式新会话有 bash 工具;②假 pwsh 调用被拒(deny 理由指路);③`gitbash_pwsh` 跑真 pwsh 7;④`gb_grep` 出参 /c/ 方言。v0.35.0 的真机验证项(见该条目)顺延至此版一起验。
16
+
17
+ ## v0.35.1 — 2026-10-12
18
+
19
+ **类型**:fix(dsh-ptc-cordis-preset 联动显示;真机反馈)。
20
+
21
+ - **`gitBash` 能力加法字段 `inlinePatched`**(win32 恒 true):v0.35.0 的行内补位使**官方四个模式全部自带 Git Bash**,对端名录里 `PTC 创造模式 · Git Bash` 这类后缀不再有区分意义,反而与已下线的变体命名风格混淆(用户真机反馈)。对端(≥ v0.17.3)读到该字段即用普通名称;旧对端忽略该字段,行为不变。加法字段演进,同 §4f 的 `pythonBackend` 先例。
22
+
6
23
  ## v0.35.0 — 2026-10-12
7
24
 
8
25
  **类型**:feat(重大行为变更)。官方四个模式**默认直接获得 Git Bash**——四个「·Git Bash」变体 preset 转为可选开关(默认关);新增真正的 `gitbash_pwsh` 工具与 `gb_*` FastCtx 文件工具;与 dsh-ops 自动共存。
package/dsh.plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "dsh-external/dsh-gitbash-shell",
3
- "version": "0.35.0",
3
+ "version": "0.35.2",
4
4
  "main": "./src/index.js",
5
5
  "description": "Windows 全模式 Git Bash:用 Git for Windows bash 替换 PowerShell 执行器;官方四个模式经行内工具补位直接获得 Git Bash(v0.35.0,默认),四个 *·Git Bash 变体 preset 可开关;附带 gitbash_pwsh(系统 PowerShell 7)与 gb_* FastCtx 文件工具,检测到 dsh-ops 自动让位。联动:dsh-ptc-cordis-preset(PTC 创造模式带 Git Bash 行且可去重)+ dsh-better-sidebar(终端标签自动切 Git Bash)。",
6
6
  "engines": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-gitbash-shell",
3
- "version": "0.35.0",
3
+ "version": "0.35.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": {
package/src/index.js CHANGED
@@ -1987,6 +1987,11 @@ export async function apply(ctx, config = {}) {
1987
1987
  // official presets too. Peers (dsh-ptc-cordis-preset) key their roster
1988
1988
  // wording and dedupe on exactly this fact.
1989
1989
  active: process.platform === 'win32',
1990
+ // Additive (v0.35.1): the inline tool patch gives EVERY OFFICIAL preset
1991
+ // Git Bash, so a roster suffix like "· Git Bash" no longer distinguishes
1992
+ // anything — peers reading this flag drop the suffix (dsh-ptc-cordis-preset
1993
+ // v0.17.3+); older peers ignore it and keep the v0.34 wording.
1994
+ get inlinePatched() { return process.platform === 'win32' },
1990
1995
  // Kept for existing consumers (peer plugins read `bashPath`): the resolved
1991
1996
  // interpreter. When resolution failed we report the explicit value (or the
1992
1997
  // historical default) so the ENOENT a consumer may hit names a real path —
@@ -2603,6 +2608,7 @@ export async function apply(ctx, config = {}) {
2603
2608
  try {
2604
2609
  mountInlineTools(ctx, {
2605
2610
  tools: (c) => { try { return c.get('tools') } catch { return undefined } },
2611
+ agents: (c) => { try { return c.get('agents') } catch { return undefined } },
2606
2612
  createScope: (agentCtx) => {
2607
2613
  let childCtx
2608
2614
  const fiber = agentCtx.plugin((c) => { childCtx = c })
@@ -223,6 +223,7 @@ export async function activateOfficialBash(agent, toolBashHref, createScopeFn, l
223
223
  /**
224
224
  * Mount the per-agent reconcile. `deps`:
225
225
  * - tools(ctx) -> the host tool registry (or undefined)
226
+ * - agents(ctx) -> the host agents service (or undefined)
226
227
  * - createScope(fn) -> cordis createScope
227
228
  * - allowed(session) -> danger-full-access verdict for the pwsh channel
228
229
  * - echo(text) -> result dialect echo (Windows -> MSYS), or undefined
@@ -231,15 +232,20 @@ export async function activateOfficialBash(agent, toolBashHref, createScopeFn, l
231
232
  * - pwshEnabled() -> the row switch for publishing the pwsh tool
232
233
  * - log(message) -> the plugin's tagged logger
233
234
  *
234
- * Registration decisions re-run on EVERY agent creation: a per-agent tool
235
- * face is per-agent truth, and the observed state (variant presets on, a
236
- * future official change, dsh-ops appearing) is absorbed automatically
237
- * instead of being encoded as a boot-time snapshot.
235
+ * Registration decisions re-run on EVERY agent creation AND once at mount
236
+ * time for every ALREADY-LIVE agent (the dsh-ops-proven shape,
237
+ * `installHostShellDenial`): this row can activate long after agents exist —
238
+ * a plugin hot-reload, or a host whose sessions resumed before this bundle
239
+ * row finished mounting — and an agent/created-only listener leaves every
240
+ * such agent with the fake pwsh and no bash, which is exactly the Windows
241
+ * field report behind this block. A second, INDEPENDENT layer guards the
242
+ * call itself through `tools/pre-execute`: visibility restriction can lose a
243
+ * race, and a lying pwsh that reaches its executor still runs Git Bash.
238
244
  */
239
245
  export function mountInlineTools(ctx, deps) {
240
- const { tools, createScope, allowed, spawn, pwshPath, echo, log } = deps
241
- /** agent -> { timer, scope, denyPwsh } */
242
- const agents = new Map()
246
+ const { tools, agents, createScope, allowed, spawn, pwshPath, echo, log } = deps
247
+ /** agent -> { timer, scope, denyPwsh }; named `tracked` because `agents` above is the host service */
248
+ const tracked = new Map()
243
249
  let pwshPublished = false
244
250
  let pwshDisposer = undefined
245
251
  let toolBashHrefPromise
@@ -287,17 +293,17 @@ export function mountInlineTools(ctx, deps) {
287
293
  if (attempt < 5 && !names.includes('bash') && !names.includes('pwsh')) {
288
294
  const timer = setTimeout(() => { try { reconcile(agent, { attempt: attempt + 1 }) } catch { /* never crash on a timer */ } }, 250)
289
295
  timer.unref?.()
290
- const state = agents.get(agent)
296
+ const state = tracked.get(agent)
291
297
  if (state) state.timer = timer
292
- else agents.set(agent, { timer, scope: undefined, denyPwsh: undefined })
298
+ else tracked.set(agent, { timer, scope: undefined, denyPwsh: undefined })
293
299
  return
294
300
  }
295
301
  const needs = inlineNeeds(names)
296
- let state = agents.get(agent)
302
+ let state = tracked.get(agent)
297
303
  if (state?.timer) { clearTimeout(state.timer); state.timer = undefined }
298
304
  if (!state) {
299
305
  state = { timer: undefined, scope: undefined, denyPwsh: undefined }
300
- agents.set(agent, state)
306
+ tracked.set(agent, state)
301
307
  }
302
308
  if (needs.denyPwsh && !state.denyPwsh) {
303
309
  // Deny the fake pwsh first: restrict is per-agent and idempotent, and
@@ -318,7 +324,7 @@ export function mountInlineTools(ctx, deps) {
318
324
  return
319
325
  }
320
326
  const scope = await activateOfficialBash(agent, href, createScope, log)
321
- if (scope && agents.get(agent) === state) {
327
+ if (scope && tracked.get(agent) === state) {
322
328
  state.scope = scope
323
329
  log('official bash tool activated for this agent (Git Bash host; fake pwsh denied)')
324
330
  }
@@ -334,9 +340,9 @@ export function mountInlineTools(ctx, deps) {
334
340
  }
335
341
 
336
342
  const teardown = (agent) => {
337
- const state = agents.get(agent)
343
+ const state = tracked.get(agent)
338
344
  if (!state) return
339
- agents.delete(agent)
345
+ tracked.delete(agent)
340
346
  if (state.timer) clearTimeout(state.timer)
341
347
  if (state.denyPwsh) { try { state.denyPwsh() } catch { /* already lifted */ } }
342
348
  if (state.scope) { void state.scope.dispose().catch(() => {}) }
@@ -345,14 +351,34 @@ export function mountInlineTools(ctx, deps) {
345
351
  try {
346
352
  ctx.on('agent/created', ({ agent } = {}) => { try { reconcile(agent) } catch { /* never break agent creation */ } })
347
353
  ctx.on('agent/disposed', ({ agent } = {}) => { try { teardown(agent) } catch { /* never break disposal */ } })
354
+ // MOUNT-TIME SWEEP (v0.35.2, the Windows field report): agents that
355
+ // already exist when this row mounts never fire agent/created — a plugin
356
+ // hot-reload or a host that resumed sessions before this bundle row
357
+ // finished mounting would otherwise keep the fake pwsh and no bash on
358
+ // every live agent. Same shape the proven dsh-ops denial uses.
359
+ for (const agent of agents?.(ctx)?.list?.() ?? []) {
360
+ try { reconcile(agent) } catch { /* one broken agent never blocks the rest */ }
361
+ }
362
+ // CALL-LAYER GUARD (v0.35.2, second independent layer): even when the
363
+ // per-agent visibility restriction loses a race, a `pwsh` call that
364
+ // reaches its executor runs Git Bash (ctx.shell IS Git Bash on this
365
+ // host). Deny at dispatch and name the replacements.
366
+ ctx.on('tools/pre-execute', (exec, next) => {
367
+ try {
368
+ if (exec?.name === 'pwsh') {
369
+ return { kind: 'deny', reason: 'the host pwsh tool would run Git Bash on this host (its executor was replaced). Use the bash tool for commands, or gitbash_pwsh for Windows-native work (registry, services, WMI).' }
370
+ }
371
+ } catch { /* never block the waterfall */ }
372
+ return next()
373
+ })
348
374
  } catch (error) {
349
375
  log(`agent lifecycle wiring failed: ${error?.message ?? error}`)
350
376
  return
351
377
  }
352
378
 
353
379
  ctx.effect(() => () => {
354
- for (const agent of [...agents.keys()]) teardown(agent)
355
- agents.clear()
380
+ for (const agent of [...tracked.keys()]) teardown(agent)
381
+ tracked.clear()
356
382
  unpublishPwsh()
357
383
  }, 'dsh-gitbash-shell: inline tool patching')
358
384
  }