dsh-gitbash-shell 0.39.3 → 0.39.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/CHANGELOG.md CHANGED
@@ -3,6 +3,38 @@
3
3
  > 倒序排列,新版本条目在最上面。条目格式:`## vX.Y.Z — YYYY-MM-DD` + 类型(feat / fix / docs / chore)+ 要点 + 相关链接。
4
4
  > 纪律见 AGENTS.md「变更记录纪律」:发版前先更新本文件并随版本提交;事故复盘、复现与真机验证记录也记在这里。
5
5
 
6
+ ## v0.39.4 — 2026-10-14
7
+
8
+ **类型**:feat(dsh-ops 0.3.0 互通:gb_* 退役 + 统一方言桥) / fix(docs)。
9
+
10
+ ### dsh-ops 0.3.0 互通:gb_* 退役 + 双工具家族统一 `/c/` 方言
11
+
12
+ dsh-ops(T-Auto)0.3.0 起以原生 Rust `ops_*` 文件工具(`ops_inspect_local_file`/`ops_grep`/`ops_glob`/`ops_replace`,
13
+ 走 DSH 原生 policy/sandbox/subprocess 通道)取代 FastCtx 上游,并新增 PTC 批量能力(`ops_batch`/`ops_result`,
14
+ opt-in)与跨平台(win/mac/linux)支持。本版把并存形态收敛为「各司其职 + 方言由本插件统一」:
15
+
16
+ - **`fastctxTools` 默认关(弃用声明)**:上游 FastCtx 已被亲爹弃用,`ops_*` 同职能且更强(原生沙箱 mediation、
17
+ 统一 `path` 契约、CAS 原子替换、跨平台)。默认值 true→false(schema、stub reader、live reader、client 卡
18
+ 四处同步);显式开启可恢复,已装 dsh-ops 时无论开关如何都自动让位(既有 `opsPresent` 检测,0.3.0 工具名
19
+ 未变,零改动兼容)。
20
+ - **bash 不让位(产品裁决,2026-10-14)**:dsh-ops 的 `ops_bash` 仅 full-access 会话发布且走裸 `ctx.subprocess`
21
+ (无沙箱 confine、无 unconfined 如实标注);受限会话只有本插件的 bash 可用,且两工具不同名并存无害——
22
+ 本插件继续是「全模式真 Git Bash 沙箱通道」,`ops_bash` 是 full-access 下的第二选择。
23
+ - **入参翻译扩展**:字段白名单加 `root`(ops_batch/ops_result 的路径参数,官方工具无此键,零碰撞);
24
+ 新增 `operations[]` 对象数组的 `path`/`to` 与 `protect[]` 字符串数组的嵌套翻译(同 present `files[].path`
25
+ 的先例)。模型说 `/c/...`,Rust worker 收到原生绝对路径。
26
+ - **出参成功面回流**:ops_* 六件加入 `rewriteResultPaths`——文件四件的 `items[].path`/`matches[].path`
27
+ (Rust 端所有路径字段统一叫 `path`)与 PTC 两件的 `root` 回显 `/c/`;`detailsRef` 等引用 ID 不透明不改写。
28
+ - **失败面回流**:`ERROR_CONTENT_TOOLS` 加 ops_* 六件——失败 content 与 `error.message` 里的 Windows 路径
29
+ 统一回答 `/c/`(`error.message` 走无条件 `msysEcho`,自动覆盖)。
30
+ - **提示词无需改动**:dsh-ops 的提示走 `systemPrompt.section`(与官方同挂点),本插件的 `windowsToMsys`
31
+ 源头替换覆盖全部 sections;其文案无 `C:/` 字面量(「正斜杠分隔符」与 `/c/` 方言天然兼容)。
32
+ - **已知事项**:未安装 dsh-better-sidebar 的部署,启动时宿主对探测式 patch edit 打一条
33
+ `entry "better-sidebar" not found` warn——宿主 patch 引擎对缺失行的固有行为,插件侧无法在保留该 edit
34
+ 功能(boot 层侧栏终端 shell 默认)的前提下去除,README 已说明。
35
+ - **测试**:新增 dsh-ops 互通矩阵(入参 root/operations/protect、出参 items/matches/root、失败 content/message、
36
+ wrapper 门控源码断言、fastctxTools 默认关断言);全量 **172/172**(+1 win32-only skip)。
37
+
6
38
  ## v0.39.3 — 2026-10-14
7
39
 
8
40
  **类型**:fix(自有 bash 工具的宿主服务兼容 #27、MSYS 参数/编码提示、源码路径边界与 unconfined 可观测性) / test / docs。
package/README.md CHANGED
@@ -36,10 +36,14 @@ POSIX 宿主上完整保留;
36
36
  会话隐藏,主机保持 bash 优先;开启后它直接可见可用——无需安装任何额外组件。
37
37
  旧 PowerShell 7 旁路工具(v0.37–v0.38.5 的独立工具)已随 v0.39.0 新架构退役;
38
38
  命令仍然首选 bash 工具。
39
- - **`gb_*` FastCtx 文件工具(默认开)**:`gb_inspect_local_file`/`gb_grep`/
39
+ - **`gb_*` FastCtx 文件工具(默认关,v0.39.4 起弃用)**:`gb_inspect_local_file`/`gb_grep`/
40
40
  `gb_glob`/`gb_replace`(Rust 实现:批量范围读取带 token 预算、grep 截断
41
41
  标注、编码探测)。它们是官方 read/grep/glob/edit 的**补充**——官方工具接
42
- 文件沙箱,仍是默认推荐;已安装 dsh-ops 时本组自动让位。
42
+ 文件沙箱,仍是默认推荐。**dsh-ops 0.3.0 起原生 `ops_*` 文件工具取代了
43
+ FastCtx 上游,本组默认关闭**(`fastctxTools` 显式开启可恢复);已安装
44
+ dsh-ops 时无论开关如何都会自动让位。与 dsh-ops 并存时本插件继续负责
45
+ `/c/` 方言统一:`ops_*` 的入参(含 `root`/`operations[].path`/`protect`)
46
+ 自动转换、返回与报错路径自动回流 `/c/`。
43
47
 
44
48
  四个「· Git Bash」**变体 preset 仍保留,默认关闭**(`variantPresets` 开关,
45
49
  设置卡可开):开启后注册 `standard-gitbash`/`minimal-gitbash`/`code-gitbash`/
package/README_EN.md CHANGED
@@ -10,9 +10,10 @@
10
10
  > on a PRIVATE Git Bash executor instance, gives the four OFFICIAL presets
11
11
  > Git Bash through a per-agent inline tool patch (v0.35.0), keeps the
12
12
  > official `pwsh` tool (REAL PowerShell) hidden behind a switch (`pwshTool`,
13
- > off by default), publishes the `gb_*` FastCtx file
14
- > tools, and keeps the four `· Git Bash` variant presets behind a switch
15
- > (`variantPresets`, off by default).
13
+ > off by default), keeps the `gb_*` FastCtx file tools behind a switch
14
+ > (`fastctxTools`, off by default since v0.39.4 — dsh-ops 0.3.0's native
15
+ > `ops_*` tools superseded FastCtx), and keeps the four `· Git Bash` variant
16
+ > presets behind a switch (`variantPresets`, off by default).
16
17
 
17
18
  ### Host requirement & preset alignment
18
19
 
@@ -60,8 +61,12 @@ The old "replace the executor" three-step patch became runtime work:
60
61
  tool face says "pwsh yes / bash no"; the official `pwsh` tool (REAL
61
62
  PowerShell) stays hidden behind the `pwshTool` switch (off by default);
62
63
  the `gb_*`
63
- FastCtx tools
64
- (`src/fastctx-bridge.js`) publish alongside. With the switch on, it registers the four
64
+ FastCtx tools (`src/fastctx-bridge.js`) publish alongside — **retired to
65
+ an explicit opt-in from v0.39.4** (dsh-ops 0.3.0 replaced FastCtx with
66
+ native `ops_*` file tools; when dsh-ops is present the bridge stands down
67
+ regardless, and this plugin keeps the `/c/` dialect unified across both
68
+ tool families by translating `ops_*` arguments and echoing their results
69
+ and errors back in MSYS form). With the switch on, it registers the four
65
70
  preset variants **declaratively** (`ctx.agentPresets.register`,
66
71
  v0.32.0+) — the row sets are committed, diffable JS data
67
72
  (`src/compositions.js`) and **no directory is ever materialized**;
package/dsh.plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "dsh-external/dsh-gitbash-shell",
3
- "version": "0.39.3",
3
+ "version": "0.39.4",
4
4
  "main": "./src/index.js",
5
5
  "description": "Windows 全模式 Git Bash:自带 bash 工具(私有 Git Bash 执行器实例,官方 PowerShell 执行器原样保留),官方四个模式经行内工具补位直接获得 Git Bash(v0.35.0 起;v0.39.0 起不再替换宿主执行器),四个 *·Git Bash 变体 preset 可开关;官方 pwsh 工具默认隐藏、开关可见(真 PowerShell);附带 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.39.3",
3
+ "version": "0.39.4",
4
4
  "description": "Windows 上给 agent 一个真正的 Git Bash 命令工具:私有 Git Bash 执行器实例(官方 PowerShell 执行器原样保留),统一 MSYS 盘根路径方言,官方 pwsh 工具默认隐藏可开关,物化四个内置预设的 Git Bash 变体,侧栏终端接管可开关。· Give agents a real Git Bash tool on Windows: a private Git Bash executor instance (the official PowerShell executor stays untouched), a unified MSYS drive-root path dialect, an opt-in official pwsh tool, and four Git Bash preset variants.",
5
5
  "type": "module",
6
6
  "repository": {
package/src/client.js CHANGED
@@ -2299,7 +2299,8 @@ window.__ModuleLoader__.load({
2299
2299
  writeField exactly like every other row on this card. */
2300
2300
  var variantOn = snap.value.variantPresets === true;
2301
2301
  var pwshOn = snap.value.pwshTool !== false;
2302
- var fctxOn = snap.value.fastctxTools !== false;
2302
+ // Default OFF (v0.39.4): must match the host's `=== true` reader.
2303
+ var fctxOn = snap.value.fastctxTools === true;
2303
2304
 
2304
2305
  var modeSection = fold("sec.mode",
2305
2306
  E("div", { className: "gb-row" },
package/src/index.js CHANGED
@@ -517,7 +517,7 @@ export const Config = Schema === null ? undefined : Schema.object({
517
517
  * Default ON; Windows-only payload, stands down entirely when dsh-ops is
518
518
  * present (its ops_* tools already cover the same ground).
519
519
  */
520
- fastctxTools: live(Schema.boolean().default(true)),
520
+ fastctxTools: live(Schema.boolean().default(false)),
521
521
  })
522
522
 
523
523
  /** Read one Config value across eras: Volatile ref (>= 0.1.7) or plain value. */
@@ -582,8 +582,8 @@ function makeLiveReader(ctx, config) {
582
582
  variantPresets: valueOf(config.variantPresets) === true,
583
583
  // Default ON (v0.35.0): the pwsh replacement fills the denied host tool.
584
584
  pwshTool: valueOf(config.pwshTool) !== false,
585
- // Default ON (v0.35.0): the FastCtx bridge stands down when dsh-ops is present.
586
- fastctxTools: valueOf(config.fastctxTools) !== false,
585
+ // Default OFF (v0.39.4, dsh-ops 0.3.0): FastCtx is dead upstream and dsh-ops ships stronger native ops_* file tools; an explicit true re-enables the bridge. It still stands down when dsh-ops is present.
586
+ fastctxTools: valueOf(config.fastctxTools) === true,
587
587
  }
588
588
  }
589
589
  return {
@@ -593,7 +593,7 @@ function makeLiveReader(ctx, config) {
593
593
  autoTerminalShell: () => dialect().autoTerminalShell !== false,
594
594
  variantPresets: () => (hasConfig ? valueOf(config.variantPresets) === true : false),
595
595
  pwshTool: () => (hasConfig ? valueOf(config.pwshTool) !== false : true),
596
- fastctxTools: () => (hasConfig ? valueOf(config.fastctxTools) !== false : true),
596
+ fastctxTools: () => (hasConfig ? valueOf(config.fastctxTools) === true : false),
597
597
  // Default OFF: only an explicit true asks for deduplication.
598
598
  suppressPeerCordis: () => (hasConfig ? valueOf(config.suppressPeerCordis) === true : false),
599
599
  }
@@ -773,7 +773,9 @@ function pathEcho(value, env) {
773
773
  * content VERBATIM: output is data, never rewritten (the same red line as
774
774
  * file content in successful results).
775
775
  */
776
- const ERROR_CONTENT_TOOLS = new Set(['read', 'read_image', 'write', 'edit', 'glob', 'grep', 'present'])
776
+ // ops_* six (v0.39.4, dsh-ops 0.3.0): their failures quote Windows paths too —
777
+ // the error content and error.message must answer in the same /c/ dialect.
778
+ const ERROR_CONTENT_TOOLS = new Set(['read', 'read_image', 'write', 'edit', 'glob', 'grep', 'present', 'ops_inspect_local_file', 'ops_glob', 'ops_grep', 'ops_replace', 'ops_batch', 'ops_result'])
777
779
 
778
780
  export function rewriteErrorContent(content, options) {
779
781
  if (!Array.isArray(content)) return content
@@ -868,6 +870,37 @@ export function rewriteResultPaths(name, value, env) {
868
870
  })
869
871
  return changed ? { ...value, files } : value
870
872
  }
873
+ // v0.39.4 (dsh-ops 0.3.0): ops_* tools answer with the same paths the
874
+ // model must keep speaking /c/ about. Their Rust worker names every path
875
+ // field `path` (items[] / matches[]) and ops_batch/ops_result echo the
876
+ // operation `root`; detailsRef ids are opaque, never rewritten.
877
+ if (name === 'ops_grep' && Array.isArray(value.matches)) {
878
+ let changed = false
879
+ const matches = value.matches.map((match) => {
880
+ if (!match || typeof match !== 'object' || typeof match.path !== 'string' || match.path === '') return match
881
+ const out = toMsys(match.path)
882
+ if (out === match.path) return match
883
+ changed = true
884
+ return { ...match, path: out }
885
+ })
886
+ return changed ? { ...value, matches } : value
887
+ }
888
+ if ((name === 'ops_inspect_local_file' || name === 'ops_glob' || name === 'ops_replace') && Array.isArray(value.items)) {
889
+ let changed = false
890
+ const items = value.items.map((item) => {
891
+ if (!item || typeof item !== 'object' || typeof item.path !== 'string' || item.path === '') return item
892
+ const out = toMsys(item.path)
893
+ if (out === item.path) return item
894
+ changed = true
895
+ return { ...item, path: out }
896
+ })
897
+ return changed ? { ...value, items } : value
898
+ }
899
+ if ((name === 'ops_batch' || name === 'ops_result') && typeof value.root === 'string' && value.root !== '') {
900
+ const out = toMsys(value.root)
901
+ if (out !== value.root) return { ...value, root: out }
902
+ return value
903
+ }
871
904
  if (name === 'glob' && Array.isArray(value.paths)) {
872
905
  let changed = false
873
906
  const paths = value.paths.map((p) => {
@@ -909,7 +942,9 @@ export function rewriteResultPaths(name, value, env) {
909
942
 
910
943
  const MSYS_DRIVE_ROOT = /^\/([a-z])\/(.*)$/i
911
944
  const MSYS_BARE_DRIVE = /^\/([a-z])$/i
912
- const TRANSLATABLE_PATH_FIELDS = new Set(['file_path', 'path', 'workdir'])
945
+ // 'root' (v0.39.4): dsh-ops ops_batch/ops_result name their path argument `root`;
946
+ // no official tool uses that key, so the extra alias is collision-free.
947
+ const TRANSLATABLE_PATH_FIELDS = new Set(['file_path', 'path', 'workdir', 'root'])
913
948
 
914
949
  // ── virtual mounts + env (v0.17.0) ─────────────────────────────────────
915
950
  //
@@ -1143,6 +1178,37 @@ export function translatePathArguments(args, env) {
1143
1178
  changed = true
1144
1179
  }
1145
1180
  }
1181
+ // v0.39.4 (dsh-ops 0.3.0): ops_batch carries its paths one level down —
1182
+ // operations[] objects with path/to, and protect[] as a plain string
1183
+ // array. Same deal as present's files[].path: the field-name whitelist
1184
+ // only sees top-level keys. The model speaks /c/ everywhere; these
1185
+ // arrive at the Rust worker as native absolute paths.
1186
+ if (Array.isArray(next.operations)) {
1187
+ const operations = next.operations.map((operation) => {
1188
+ if (!operation || typeof operation !== 'object') return operation
1189
+ let out = operation
1190
+ for (const field of ['path', 'to']) {
1191
+ if (typeof out[field] !== 'string') continue
1192
+ const translated = translateMsysPath(out[field], env)
1193
+ if (translated !== out[field]) out = { ...out, [field]: translated }
1194
+ }
1195
+ return out
1196
+ })
1197
+ if (operations.some((operation, index) => operation !== next.operations[index])) {
1198
+ next.operations = operations
1199
+ changed = true
1200
+ }
1201
+ }
1202
+ if (Array.isArray(next.protect)) {
1203
+ const protect = next.protect.map((entry) => {
1204
+ if (typeof entry !== 'string') return entry
1205
+ return translateMsysPath(entry, env)
1206
+ })
1207
+ if (protect.some((entry, index) => entry !== next.protect[index])) {
1208
+ next.protect = protect
1209
+ changed = true
1210
+ }
1211
+ }
1146
1212
  return changed ? next : args
1147
1213
  }
1148
1214
 
@@ -285,19 +285,20 @@ export function mountInlineTools(ctx, deps) {
285
285
  const fiber = scope.ctx.plugin(module)
286
286
  await untilDisposed(fiber, state.controller.signal)
287
287
  if (!isLive(agent, state)) return
288
- retained = true
289
288
  // An INACTIVE fiber resolves its await() without ever running apply()
290
289
  // (issue #27): awaiting the plugin proves nothing, so read the registry
291
- // back before claiming success. A miss gets an explicit failure line —
292
- // never a silent no-tool session behind a success-shaped log.
290
+ // back before claiming success. A miss THROWS — the catch below logs the
291
+ // explicit activation failure and the finally disposes this half-mounted
292
+ // scope instead of leaking it for the agent's lifetime (shape credit:
293
+ // KouzakiUmi's PR #28).
293
294
  const registeredNames = scope.ctx.get !== undefined
294
295
  ? ((scope.ctx.get('tools')?.schemas?.(agent) ?? []).map((schema) => schema && typeof schema.name === 'string' ? schema.name : ''))
295
296
  : []
296
- if (registeredNames.includes('bash')) {
297
- log('own bash tool activated for this agent (Git Bash execution; official pwsh tool real but hidden by default)')
298
- } else {
299
- log('own bash tool did NOT register for this agent: the module fiber likely went INACTIVE (this host lacks an injected service the module requires) or the tools read-back is unavailable — bash is NOT available in this session; please report this with your dsh version (issue #27 pattern)')
297
+ if (registeredNames.includes('bash') === false) {
298
+ throw new Error('bash tool did not register for this agent (module fiber INACTIVE — this host lacks an injected service the module requires — or the tools read-back is unavailable); bash is NOT available in this session, please report this with your dsh version (issue #27)')
300
299
  }
300
+ retained = true
301
+ log('own bash tool activated for this agent (Git Bash execution; official pwsh tool real but hidden by default)')
301
302
  } catch (error) {
302
303
  if (isLive(agent, state)) log(`own bash tool activation failed for this agent: ${error?.message ?? error}`)
303
304
  } finally {