dsh-gitbash-shell 0.39.2 → 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 +94 -0
- package/README.md +44 -16
- package/README_EN.md +64 -23
- package/dsh.plugin.json +1 -1
- package/package.json +2 -2
- package/src/bash-render.js +21 -10
- package/src/bash-tool.js +53 -11
- package/src/client.js +2 -1
- package/src/index.js +94 -17
- package/src/inline-tools.js +12 -0
- package/src/shell.js +14 -14
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,100 @@
|
|
|
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
|
+
|
|
38
|
+
## v0.39.3 — 2026-10-14
|
|
39
|
+
|
|
40
|
+
**类型**:fix(自有 bash 工具的宿主服务兼容 #27、MSYS 参数/编码提示、源码路径边界与 unconfined 可观测性) / test / docs。
|
|
41
|
+
|
|
42
|
+
### issue #27:自有 bash 工具在无 `workingDirectory` 服务的宿主上永不注册
|
|
43
|
+
|
|
44
|
+
- **根因(route B 镜像的契约盲区)**:自有 bash 工具镜像的是新版宿主(`0.2.1-alpha.x`)官方 tool-bash
|
|
45
|
+
的 `inject` 列表(含 `workingDirectory`),而兼容矩阵声明的 `0.2.0-rc.2` 上**没有该服务**——cordis 对
|
|
46
|
+
无法满足的 inject 让模块 fiber **永久 INACTIVE**:`apply()` 从不执行、工具永不注册、零错误日志,且
|
|
47
|
+
inline-tools 的激活日志照打(`await` 一个 INACTIVE fiber 会正常 resolve)——**假阳性成功**。
|
|
48
|
+
与 v0.24.0 settingsScope、v0.24.3 静态 peer import 同一失败类(静默失效、宿主日志全绿)。
|
|
49
|
+
- **修复**:① `inject` 收敛为 `['tools', 'systemPrompt', 'shellEnv']`(服务最小面,`workingDirectory`
|
|
50
|
+
改为使用点 `ctx.get()` 可选读取);② cwd 接缝双向兼容——有 `workingDirectory` 的宿主走
|
|
51
|
+
`ensure()`(与新版官方一致),没有的宿主退回 rc.2 时代官方 `resolveWorkdir` 的同一条接缝
|
|
52
|
+
(`policy.workspaceRoot ?? session.header.cwd`),**不再落到宿主进程目录**;③ 激活日志必须经
|
|
53
|
+
`tools.schemas(agent)` 回读确认 `bash` 真正在册,不在册打显式失败行(点名 INACTIVE 可能性 +
|
|
54
|
+
请携带 dsh 版本报告)——**消除假阳性**。
|
|
55
|
+
- **教训(§4l 追记⑧)**:镜像宿主模块时,`inject` 面是**契约面**——必须取兼容矩阵内**最老宿主**的
|
|
56
|
+
形状,或一律可选读取;「宿主新版本有」不等于「矩阵内都有」。
|
|
57
|
+
- 报告者 largoalladonzella 提供了完整根因链(asar 字节级取证 + cordis `_refresh`/`_setEpoch` 源码 +
|
|
58
|
+
官方两版 inject 对照)与本机验证补丁;本仓库按其建议实现并补真断言。
|
|
59
|
+
- 新增回归:无 `workingDirectory` 的最小 ctx 上 `apply()` 照常注册 + cwd 回退正确;有服务的宿主
|
|
60
|
+
`ensure()` 接缝不变;源码断言锁死硬 inject 列表与「回读门控激活日志」。本地全量 **168/168**(+1
|
|
61
|
+
win32-only skip)。
|
|
62
|
+
|
|
63
|
+
### PR #24:超时转后台的独立 sandbox 警示
|
|
64
|
+
|
|
65
|
+
- promoted 返回值显式携带可选 sandbox facts,输出 schema 保持闭合;defineTool 与 JSON-schema 回退共用此契约。
|
|
66
|
+
- `renderPromoted` 自行追加 sandbox notes,不再依赖 output 预先含警示;promotion 输出保留丢失/spill 提示,
|
|
67
|
+
避免重复追加 sandbox 警示。无 sandbox/full-access 的原反馈保持不变,denied/runnerFailed 分支继续保留。
|
|
68
|
+
- 纯回归改用不含警示的原始文本,覆盖空输出、full-access、拒绝与 runner failure。
|
|
69
|
+
新增实际 ToolRuntime→私有执行器→内存 provider 的 promotion 回归(Jobs wait/read 为受控桩),
|
|
70
|
+
验证 RO/WW 警示各出现一次、full 无警示、DTO 与最终模型文本分离、丢失/spill 提示及 root 服务不变。
|
|
71
|
+
- 验证:普通完整回归 **166/166**、实际安装Core完整preview **18/18**,均exit0,无skip/cancel;
|
|
72
|
+
语法与diff检查通过。不等同于生产GUI或真实Jobs时钟验收。
|
|
73
|
+
|
|
74
|
+
### MSYS 兼容边界尝试修复
|
|
75
|
+
|
|
76
|
+
- **范围**:从 [`6b26ded`](https://github.com/KouzakiUmi/dsh-gitbash-shell/commit/6b26ded6cc3a2a6113579149286047629b433c48)
|
|
77
|
+
开出 `fix/msys-compatibility-boundaries`;不改 root PowerShell、market patch、生产 Profile、
|
|
78
|
+
安全默认或版本号。原生参数转换/混合编码/Windows shell confinement 不能在这层安全地普遍修好,
|
|
79
|
+
因此修工具提示与可观测性,不全局 EXCL=*、不猜码、不将警示冒充沙箱修复。
|
|
80
|
+
- **源码路径**:run_code 的完整 Unicode 字母/组合字符/数字和含 `#` 文件名字面量走共享挂载表;
|
|
81
|
+
合法 EOF 行注释不再让整份重写弃权;tagged templates(含 String.raw、调用/索引 tag)保持原样,
|
|
82
|
+
避免 raw 设备路径被错误转义;of/await/yield 等上下文词可作 tag 时保守不改。
|
|
83
|
+
未闭合/不确定正则与除法仍整份 no-op,未扩展插值或动态表达式猜测。
|
|
84
|
+
词法层仍无法识别非路径数据的业务用途,提示明确建议直接 Node fs/path 用 native/cwd-relative 路径。
|
|
85
|
+
- **提示门控**:真实 prompt listener 读取 live codePaths;OFF 不再承诺程序字符串会被翻译,
|
|
86
|
+
ON 改为 best-effort 并指出模板、动态/非路径数据限制。关闭 codePaths 不改变工具路径字段翻译。
|
|
87
|
+
- **原生互操作**:登记的 bash 描述无条件说明 `/all`/`/c` 转换、双斜杠/单命令 EXCL 前缀及其
|
|
88
|
+
对 `/c/...` 的影响;检查预期效果而非只看 exit0。说明 UTF-8 捕获与 CP936/GBK 不匹配,
|
|
89
|
+
显式转码须在字节捕获前且保持退出状态;locale 与全流猜码不是普遍修复。
|
|
90
|
+
- **unconfined 可见性**:执行句柄在等待 result() 前带 sandbox facts;前台正文、后台 outcome/
|
|
91
|
+
增量嵌入与超时转后台显示警示。后台登记输出联合新增可选 sandbox facts,指出计划执行边界,
|
|
92
|
+
不把未启动的 job 宣称为已经执行。保留原请求 mode,不把 unconfined 当 denial/escalation,
|
|
93
|
+
并明确不得通过 shell 绕过文件工具策略拒绝。双语说明区分文件读能力与 write/edit 围栏,
|
|
94
|
+
删除“所有未来版本都无解”的过度结论。
|
|
95
|
+
- **验证**:新增9项兼容回归和1项后台登记回归,普通全量 **166/166**;新增3项实际 Core
|
|
96
|
+
ToolRuntime/执行句柄/run_code transport 回归,完整无头 preview **17/17**。旧护栏保留;打包 dry-run、语法和 diff
|
|
97
|
+
检查通过。新增 preview 使用实际宿主 API 与内存 subprocess,已有 preview 保留真实 Git Bash/
|
|
98
|
+
PowerShell 进程验收;不等同于生产 GUI、真实 PTC 引擎或新 confinement 方案验证。
|
|
99
|
+
|
|
6
100
|
## v0.39.2 — 2026-10-13
|
|
7
101
|
|
|
8
102
|
**类型**:fix(route B 工具/预设生命周期、动态配置与客户端回归) / test。
|
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 文件工具(
|
|
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
|
-
|
|
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`/
|
|
@@ -118,20 +122,19 @@ bundle patch(`cordis.patch.yml`)是**纯新增**(v0.39.0 起):只插入本插件
|
|
|
118
122
|
**Windows 宿主上的事实**:`read-only` / `workspace-write` 权限预设下的 **shell 命令**,
|
|
119
123
|
实际以**不受限(unconfined)方式执行**:
|
|
120
124
|
|
|
121
|
-
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
shell 能写出工作区、文件工具不能,**两者的边界并不一致**。
|
|
125
|
+
- **为什么**:已验证的 Git Bash/MSYS 组合在现有 restricted-token 后端下初始化失败,
|
|
126
|
+
历史复现为 `msys-2.0.dll` 的 cygheap/signal-pipe 对象权限与 restricting-SID
|
|
127
|
+
二次写检查不兼容(Win32 error 5 / 0xC0000142)。当前没有提供可用的受限 Git Bash
|
|
128
|
+
实现,不声称所有未来运行时或隔离方案都无解。本插件保留**如实标注的绕过**;
|
|
129
|
+
没有静默回退 pwsh/cmd/WSL,本次兼容修复也没有恢复 shell 写入围栏。
|
|
130
|
+
- **如何标注**:结果元数据保留
|
|
131
|
+
`sandbox: { mode: "<请求的模式>", denied: false, enforcement: "unconfined" }`;
|
|
132
|
+
工具描述、前台正文、后台登记及结果摘要、超时转后台反馈都明确警示 shell 写限制未生效。
|
|
133
|
+
后台登记标明的是计划采用的执行边界,不代表命令已经运行;实例首次调用另有日志。
|
|
134
|
+
- **文件工具是另一层**:官方 fs-tool policy 对 write/edit 保留策略检查:
|
|
135
|
+
`read-only` 拒绝修改,`workspace-write` 只允许声明的工作区和平台临时可写根。
|
|
136
|
+
read/glob/grep 等读取并非一律限制在工作区内。shell 进程不受这些工具检查约束,
|
|
137
|
+
**两者的边界并不一致**;绝不能用 shell 绕过文件工具已返回的策略拒绝。
|
|
135
138
|
- **可观察的对照**(相对 pwsh-sandbox 时代):会话进程的完整性级别不再被压低
|
|
136
139
|
(Low → Medium);`$env:TEMP` 不再被重定向到 per-session 隔离目录。
|
|
137
140
|
|
|
@@ -396,6 +399,31 @@ PTC/创造及用户自建模式一律覆盖。
|
|
|
396
399
|
都不再改写,文件工具收 Windows 路径。**bash 始终是 Git Bash,不受此开关影响**——它只决定
|
|
397
400
|
跨工具的路径方言。
|
|
398
401
|
|
|
402
|
+
## 原生 Windows 程序与 run_code 的兼容边界
|
|
403
|
+
|
|
404
|
+
工具描述始终警示下面的参数、编码和安全限制;这些限制不随路径方言关闭而消失。
|
|
405
|
+
插件**不会自动改写 command、全局关闭 MSYS 参数转换或猜测输出编码**。
|
|
406
|
+
|
|
407
|
+
- **斜杠开关**:MSYS 在启动原生 Windows 程序时可能把 `/all`、`/c` 当路径。
|
|
408
|
+
本机已验证 `ipconfig //all`、`cmd //c` 或单命令
|
|
409
|
+
`MSYS2_ARG_CONV_EXCL='/all' ipconfig /all` 可保留开关。
|
|
410
|
+
排除值是前缀:排除 `/c` 也会影响真正的 `/c/...` 路径,该次原生调用应明确用
|
|
411
|
+
Windows drive-letter 路径。不要全局设置 `MSYS2_ARG_CONV_EXCL='*'`。
|
|
412
|
+
`cmd /c` 被转换后可能 exit0 却没有执行所需命令,应检查预期效果而非只看退出码。
|
|
413
|
+
- **输出编码**:宿主按 UTF-8 解码;原生 Windows 程序可能输出 CP936/GBK 或其他代码页。
|
|
414
|
+
先使用程序自己的 UTF-8 输出选项,或在捕获前按已知编码显式转换字节,保持原退出状态。
|
|
415
|
+
`LANG/LC_ALL` 不保证改变原生程序的代码页。不要全流默认 GBK 或依替换字符数自动猜码:
|
|
416
|
+
同一 Bash 调用可能混合 UTF-8/GBK,已有乱码字符串也不能可靠恢复原字节。
|
|
417
|
+
- **run_code**:`codePaths` 默认开,与 `posixPaths`/委托 gate 同时生效时,完整、可判定的
|
|
418
|
+
普通路径字面量按共享挂载表尽力转换,包括中文/组合字符/空格/`#` 文件名。
|
|
419
|
+
EOF 行注释不再阻止转换;tagged template(包括 String.raw)保持原样,避免破坏 raw/tag 语义。
|
|
420
|
+
插值模板、转义路径、不确定的正则/除法语法仍可能不改;不确定时整份 no-op。
|
|
421
|
+
直接调用 Node fs/path 时优先明确的 Windows drive-letter 或 cwd-relative 路径;
|
|
422
|
+
工具 path/file_path 字段仍走工具参数翻译。词法扫描不能识别路由、搜索数据或 shell
|
|
423
|
+
command 的用途;此类字符串不要依赖自动重写,必要时关闭 `codePaths`(提示同步标为 OFF)。
|
|
424
|
+
- **沙箱**:参见上方安全章节。警示可见性得到修复,但 Windows shell 的 unconfined
|
|
425
|
+
取舍未改变,文件工具的策略拒绝绝不授权 shell 重试。
|
|
426
|
+
|
|
399
427
|
## 与 dsh-better-sidebar 联动
|
|
400
428
|
|
|
401
429
|
装了 [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar)(v0.15.2+) 时,Windows 上本插件会通过**它的官方设置补丁口**(运行时 `terminalShell` 设置,对方文档明示"settings-page overrides win for terminals opened afterwards")把它打开的终端 shell 指向 Git Bash——**UI 终端标签与模型侧 `terminal_*` 工具统一生效**,不改对方一行代码、新开的终端即生效。此外,**本插件的 bundle patch 还会给对方的行补上 `config.shell`**(启动期解析,连 **tab 标题也会显示 bash**;对方未安装时该行无害跳过)。规则:
|
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),
|
|
14
|
-
>
|
|
15
|
-
>
|
|
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
|
-
|
|
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**;
|
|
@@ -93,24 +98,26 @@ restricted tokens, workspace fencing, denial classification, identical to
|
|
|
93
98
|
**On Windows**, shell commands under the `read-only` / `workspace-write`
|
|
94
99
|
permission presets actually run **UNCONFINED**:
|
|
95
100
|
|
|
96
|
-
- **Why**:
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
- **
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
the workspace
|
|
101
|
+
- **Why**: tested Git Bash/MSYS combinations failed to initialize under the
|
|
102
|
+
existing restricted-token backend. Historical reproduction found cygheap/
|
|
103
|
+
signal-pipe object permissions incompatible with the restricting-SID write
|
|
104
|
+
check (Win32 error 5 / 0xC0000142). No working confined Git Bash implementation
|
|
105
|
+
is provided here; this is not a claim that every future runtime or isolation
|
|
106
|
+
design is impossible. The plugin retains its **labelled bypass**, with no
|
|
107
|
+
silent fallback to pwsh/cmd/WSL. Compatibility fixes do not restore confinement.
|
|
108
|
+
- **How it is labelled**: results retain
|
|
109
|
+
`sandbox: { mode: "<requested mode>", denied: false, enforcement: "unconfined" }`.
|
|
110
|
+
The tool description, foreground text, initial background registration,
|
|
111
|
+
background outcome and timeout-promotion feedback warn that shell write
|
|
112
|
+
restrictions are not enforced. Initial registration describes the planned
|
|
113
|
+
boundary, not a claim that the command has already run; there is also a
|
|
114
|
+
one-time notice per executor instance.
|
|
115
|
+
- **Official file tools have a separate policy**: write/edit still reject
|
|
116
|
+
mutations under `read-only`; `workspace-write` checks the declared workspace
|
|
117
|
+
and platform writable temp roots. Reads such as read/glob/grep are not all
|
|
118
|
+
limited to the workspace. Shell processes are not governed by these tool
|
|
119
|
+
checks — **the two boundaries differ**. Never bypass a file-tool policy
|
|
120
|
+
denial through shell.
|
|
114
121
|
- **Observable differences** vs the pwsh-sandbox era: the session process's
|
|
115
122
|
integrity level is no longer lowered (Low → Medium), and `$env:TEMP` is no
|
|
116
123
|
longer redirected to a per-session directory.
|
|
@@ -125,6 +132,40 @@ official PowerShell executor was never touched by this plugin. The
|
|
|
125
132
|
> The full analysis (the 0.6.0–0.13.1 failure history and the community's two
|
|
126
133
|
> camps) is in the CHANGELOG under v0.13.2 and in the `src/shell.js` header.
|
|
127
134
|
|
|
135
|
+
## Native Windows and run_code compatibility boundaries
|
|
136
|
+
|
|
137
|
+
The tool description always warns about these argument, encoding and security
|
|
138
|
+
boundaries, independently of the path-dialect switch. The plugin does not
|
|
139
|
+
silently rewrite command source, globally disable argument conversion or guess
|
|
140
|
+
output encodings.
|
|
141
|
+
|
|
142
|
+
- **Slash options**: MSYS can treat native Windows `/all` and `/c` as paths.
|
|
143
|
+
Tested alternatives are `ipconfig //all`, `cmd //c`, or a per-command
|
|
144
|
+
`MSYS2_ARG_CONV_EXCL='/all' ipconfig /all`. Exclusions match prefixes: excluding
|
|
145
|
+
`/c` also excludes `/c/...` file paths, so explicitly use native drive-letter
|
|
146
|
+
paths for those arguments. Do not set `MSYS2_ARG_CONV_EXCL='*'` globally.
|
|
147
|
+
A converted `cmd /c` may exit0 without performing the intended action; verify
|
|
148
|
+
the expected effect, not only the exit code.
|
|
149
|
+
- **Output encodings**: the host decodes UTF-8. Native programs may emit CP936/GBK
|
|
150
|
+
or another code page. Prefer a program's UTF-8 option, or explicitly convert
|
|
151
|
+
known-encoding bytes before capture while preserving the original exit state.
|
|
152
|
+
Locale variables do not guarantee native UTF-8. Do not decode all Bash output
|
|
153
|
+
as GBK or infer encoding from replacement-character counts: one call can mix
|
|
154
|
+
UTF-8 and GBK, and an already-corrupted string cannot recover the original bytes.
|
|
155
|
+
- **run_code**: `codePaths` defaults ON and follows the total/delegate dialect
|
|
156
|
+
gates. Complete compatible plain path literals are rewritten best-effort with
|
|
157
|
+
the shared mount table, including Unicode letters/marks, spaces and `#` names.
|
|
158
|
+
Legal EOF line comments no longer prevent rewriting; tagged templates (such
|
|
159
|
+
as String.raw) retain their data and raw semantics. Interpolation, escapes,
|
|
160
|
+
uncertain regex/division syntax may remain unchanged; uncertainty abandons the
|
|
161
|
+
entire rewrite. Prefer native drive-letter or cwd-relative paths for direct
|
|
162
|
+
Node fs/path APIs; typed tool path arguments remain translated separately.
|
|
163
|
+
Lexical scanning cannot distinguish routes/search data/shell command strings
|
|
164
|
+
from paths. Do not rely on rewriting those values; disable `codePaths` when
|
|
165
|
+
needed (the model hint now accurately states OFF).
|
|
166
|
+
- **Security**: warnings are now visible, but Windows unconfined behavior is
|
|
167
|
+
unchanged. A file-tool policy denial never authorizes a shell retry.
|
|
168
|
+
|
|
128
169
|
## Config
|
|
129
170
|
|
|
130
171
|
The `gitbash-shell` row (its id has matched the settings namespace since v0.24.0; it was
|
package/dsh.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-external/dsh-gitbash-shell",
|
|
3
|
-
"version": "0.39.
|
|
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
|
+
"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": {
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"icon.svg"
|
|
53
53
|
],
|
|
54
54
|
"scripts": {
|
|
55
|
-
"test": "node --test tests/smoke.mjs tests/private-executor.mjs tests/client-regressions.mjs tests/bridge-regressions.mjs tests/runtime-regressions.mjs tests/host-regressions.mjs",
|
|
55
|
+
"test": "node --test tests/smoke.mjs tests/private-executor.mjs tests/client-regressions.mjs tests/bridge-regressions.mjs tests/runtime-regressions.mjs tests/host-regressions.mjs tests/compatibility-regressions.mjs",
|
|
56
56
|
"test:preview": "node --import ./tests/host-loader.mjs --test tests/preview.mjs tests/bridge-preview.mjs tests/runtime-preview.mjs tests/host-preview.mjs",
|
|
57
57
|
"version": "node -e \"const fs=require('fs');const m=JSON.parse(fs.readFileSync('dsh.plugin.json','utf8'));const v=JSON.parse(fs.readFileSync('package.json','utf8')).version;m.version=v;fs.writeFileSync('dsh.plugin.json',JSON.stringify(m,null,2)+'\\n')\" && git add dsh.plugin.json"
|
|
58
58
|
},
|
package/src/bash-render.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* here). Instead the plugin ships its OWN bash tool whose executor is a private
|
|
8
8
|
* `GitBashSandboxExecutor` instance (never registered as a service), and this
|
|
9
9
|
* file mirrors the official tool's rendering contract call-for-call so the
|
|
10
|
-
* model sees
|
|
10
|
+
* model sees the official output shapes, with an explicit unconfined warning:
|
|
11
11
|
*
|
|
12
12
|
* - `render.ts` of `@deepseek-ai/dsh-tool-bash` (result / promoted / job-read
|
|
13
13
|
* text shaping), and
|
|
@@ -28,6 +28,17 @@ export function sandboxDenialMarker(mode) {
|
|
|
28
28
|
return `[sandbox: file access denied under ${mode} mode]`
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/** A capability warning, not a denial or permission to bypass another denial. */
|
|
32
|
+
export function sandboxUnconfinedMarker(mode) {
|
|
33
|
+
return `[sandbox: Git Bash is unconfined on Windows — shell file-write restrictions are NOT enforced (requested mode: ${mode}); official file-tool write/edit policy still applies; never bypass its denial through shell]`
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Include the planned execution boundary even before the job has spawned. */
|
|
37
|
+
export function renderBackground(jobId, sandbox) {
|
|
38
|
+
const notes = sandboxNotes(sandbox, [])
|
|
39
|
+
return `started background job ${jobId}${notes.length ? '\n' + notes.join('\n') : ''}`
|
|
40
|
+
}
|
|
41
|
+
|
|
31
42
|
/** The official same-turn escalation hint (escalation.ts:84). */
|
|
32
43
|
export function escalationHintMarker(subject) {
|
|
33
44
|
return `[sandbox: escalation available — retry this exact ${subject} once with sandbox_permissions (the narrowest wider mode that suffices) + justification; the approval prompt asks the user]`
|
|
@@ -84,6 +95,7 @@ export function renderResult(result, escalationModes = []) {
|
|
|
84
95
|
markers.push(sandboxDenialMarker(result.sandbox.mode))
|
|
85
96
|
if (escalationModes.length > 0) markers.push(escalationHintMarker('command'))
|
|
86
97
|
}
|
|
98
|
+
if (result.sandbox?.enforcement === 'unconfined') markers.push(sandboxUnconfinedMarker(result.sandbox.mode))
|
|
87
99
|
// A command may trap SIGTERM and exit 0 after timeout; still report interruption.
|
|
88
100
|
if (result.timedOut) markers.push(`[timed out after ${result.timeoutMs}ms]`)
|
|
89
101
|
// A kill from outside the call (the human stopping its job) is not a
|
|
@@ -103,16 +115,19 @@ export function renderResult(result, escalationModes = []) {
|
|
|
103
115
|
/**
|
|
104
116
|
* Shape a foreground call that stopped waiting into the text the model sees.
|
|
105
117
|
* Mirrors the official `renderPromoted` (tool-bash render.ts:77).
|
|
106
|
-
* @param {{ jobId: string, timeoutMs: number, output: string }} promoted
|
|
118
|
+
* @param {{ jobId: string, timeoutMs: number, output: string, sandbox?: object }} promoted
|
|
119
|
+
* @param {string[]} [escalationModes]
|
|
107
120
|
* @returns {string}
|
|
108
121
|
*/
|
|
109
|
-
export function renderPromoted(promoted) {
|
|
122
|
+
export function renderPromoted(promoted, escalationModes = []) {
|
|
110
123
|
const body = promoted.output.length > 0
|
|
111
124
|
? (promoted.output.endsWith('\n') ? promoted.output : `${promoted.output}\n`)
|
|
112
125
|
: ''
|
|
113
|
-
|
|
126
|
+
const text = `${body}[still running after ${promoted.timeoutMs}ms; moved to background job ${promoted.jobId}]\n`
|
|
114
127
|
+ 'The command keeps running in the background. You will be notified when it finishes; '
|
|
115
128
|
+ 'read newer output with job_output, stop it with job_kill.'
|
|
129
|
+
const notes = sandboxNotes(promoted.sandbox, escalationModes)
|
|
130
|
+
return notes.length > 0 ? `${text}\n${notes.join('\n')}` : text
|
|
116
131
|
}
|
|
117
132
|
|
|
118
133
|
/**
|
|
@@ -129,6 +144,7 @@ function sandboxNotes(sandbox, escalationModes) {
|
|
|
129
144
|
if (escalationModes.length > 0) notes.push(escalationHintMarker('command'))
|
|
130
145
|
return notes
|
|
131
146
|
}
|
|
147
|
+
if (sandbox?.enforcement === 'unconfined') return [sandboxUnconfinedMarker(sandbox.mode)]
|
|
132
148
|
return []
|
|
133
149
|
}
|
|
134
150
|
|
|
@@ -192,12 +208,7 @@ export function renderJobRead(delta, lossy, spillPaths, sandbox, escalationModes
|
|
|
192
208
|
const paths = Array.isArray(spillPaths) ? spillPaths : []
|
|
193
209
|
notices.push(`[some output was dropped from memory; full output: ${paths.length > 0 ? paths.join(', ') : '(unavailable)'}]`)
|
|
194
210
|
}
|
|
195
|
-
|
|
196
|
-
notices.push(`[sandbox: the sandbox runner itself failed under ${sandbox.mode} mode — the command did not run; this is a sandbox problem, not a command failure]`)
|
|
197
|
-
} else if (sandbox && sandbox.denied) {
|
|
198
|
-
notices.push(sandboxDenialMarker(sandbox.mode))
|
|
199
|
-
if (escalationModes.length > 0) notices.push(escalationHintMarker('command'))
|
|
200
|
-
}
|
|
211
|
+
notices.push(...sandboxNotes(sandbox, escalationModes))
|
|
201
212
|
if (notices.length === 0) return delta
|
|
202
213
|
return `${delta}${delta.length > 0 && !delta.endsWith('\n') ? '\n' : ''}${notices.join('\n')}`
|
|
203
214
|
}
|
package/src/bash-tool.js
CHANGED
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
import {
|
|
44
44
|
ESCALATION_TARGETS,
|
|
45
45
|
parseExitStatus,
|
|
46
|
+
renderBackground,
|
|
46
47
|
renderJobRead,
|
|
47
48
|
renderPromoted,
|
|
48
49
|
renderResult,
|
|
@@ -86,7 +87,7 @@ export async function resolveHostModule(ctx, name) {
|
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
/**
|
|
89
|
-
* The official bash description
|
|
90
|
+
* The official bash description plus unavoidable Git for Windows boundaries.
|
|
90
91
|
* @returns {string}
|
|
91
92
|
*/
|
|
92
93
|
export function bashDescription() {
|
|
@@ -97,7 +98,11 @@ export function bashDescription() {
|
|
|
97
98
|
+ 'Provide `description` before `command` in the arguments. '
|
|
98
99
|
+ 'Before any delete or move, verify that the resolved absolute target path is the intended one; never run it against a computed path you have not checked. '
|
|
99
100
|
+ 'An unset variable expands to an empty string, so guard variables in such paths with `${VAR:?}`. '
|
|
100
|
-
+ 'Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under <mode> mode]`, a policy denial: do not retry another way.'
|
|
101
|
+
+ 'Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under <mode> mode]`, a policy denial: do not retry another way. '
|
|
102
|
+
+ 'Git for Windows (MSYS) converts path-looking arguments when invoking native Windows programs: ipconfig /all and cmd /c may break; use ipconfig //all or cmd //c, or a per-command MSYS2_ARG_CONV_EXCL prefix. '
|
|
103
|
+
+ 'Exclusions are prefixes: excluding /c also excludes /c/... paths, so use native drive-letter paths for those arguments; do not disable conversion globally. Verify the expected effect as well as the exit code. '
|
|
104
|
+
+ 'Output is decoded as UTF-8; native Windows programs may emit OEM/ANSI bytes (for example CP936/GBK) and display garbled text. Use a program UTF-8 option or explicit known-encoding conversion before capture; locale variables alone do not fix native output, and mixed streams cannot safely be guessed or decoded globally as GBK. '
|
|
105
|
+
+ 'On Windows, this Git Bash route does NOT enforce shell file-write restrictions in read-only/workspace-write mode (unconfined). Official file-tool write/edit policy is separate; never use shell to bypass its denial.'
|
|
101
106
|
}
|
|
102
107
|
|
|
103
108
|
/** The official foreground/present call view (index.ts:108). */
|
|
@@ -187,9 +192,17 @@ const BACKGROUND_OUTPUT_PROPERTIES = {
|
|
|
187
192
|
kind: { type: 'string', required: true, const: 'background' },
|
|
188
193
|
jobId: { type: 'string', required: true },
|
|
189
194
|
cwd: { type: 'string', required: true },
|
|
195
|
+
sandbox: {
|
|
196
|
+
type: 'object', additionalProperties: false,
|
|
197
|
+
properties: {
|
|
198
|
+
mode: { type: 'string', required: true },
|
|
199
|
+
denied: { type: 'boolean', required: true },
|
|
200
|
+
enforcement: { type: 'string' },
|
|
201
|
+
},
|
|
202
|
+
},
|
|
190
203
|
}
|
|
191
204
|
|
|
192
|
-
/** The official output union
|
|
205
|
+
/** The official output union, with optional background sandbox facts. */
|
|
193
206
|
function outputSchemaSpec() {
|
|
194
207
|
return {
|
|
195
208
|
oneOf: [
|
|
@@ -203,6 +216,10 @@ function outputSchemaSpec() {
|
|
|
203
216
|
jobId: { type: 'string', required: true },
|
|
204
217
|
timeoutMs: { type: 'number', required: true },
|
|
205
218
|
output: { type: 'string', required: true },
|
|
219
|
+
sandbox: {
|
|
220
|
+
...BACKGROUND_OUTPUT_PROPERTIES.sandbox,
|
|
221
|
+
properties: { ...BACKGROUND_OUTPUT_PROPERTIES.sandbox.properties, runnerFailed: { type: 'boolean' } },
|
|
222
|
+
},
|
|
206
223
|
},
|
|
207
224
|
},
|
|
208
225
|
{
|
|
@@ -319,7 +336,12 @@ export function createBashToolModule({ executor, toolsModule, sandboxModule, llm
|
|
|
319
336
|
: (subject) => `The narrowest wider sandbox mode for a one-shot retry of the exact ${subject} the sandbox just denied; the retry asks the user for approval.`
|
|
320
337
|
|
|
321
338
|
return {
|
|
322
|
-
|
|
339
|
+
// workingDirectory is NOT a service on every host this plugin claims
|
|
340
|
+
// compatibility with (0.2.0-rc.2 has none — issue #27). A missing hard
|
|
341
|
+
// inject leaves the whole module fiber permanently INACTIVE: apply() never
|
|
342
|
+
// runs, the tool never registers, and cordis logs nothing. Read it
|
|
343
|
+
// optionally at the use site (below) instead of injecting it.
|
|
344
|
+
inject: ['tools', 'systemPrompt', 'shellEnv'],
|
|
323
345
|
apply(ctx, config = {}) {
|
|
324
346
|
const backgroundEnabled = config?.enableRunInBackground ?? true
|
|
325
347
|
const promoteOnTimeout = (config?.promoteOnTimeout ?? true) && backgroundEnabled
|
|
@@ -424,14 +446,17 @@ export function createBashToolModule({ executor, toolsModule, sandboxModule, llm
|
|
|
424
446
|
}
|
|
425
447
|
if (view.status === 'running' || view.status === 'stopping') {
|
|
426
448
|
const read = registry.read(attached.id, owner)
|
|
449
|
+
const sandbox = attached.process()?.sandbox
|
|
427
450
|
return {
|
|
428
451
|
kind: 'promoted',
|
|
429
452
|
cwd: spec.workdir,
|
|
430
453
|
jobId: attached.id,
|
|
431
454
|
timeoutMs,
|
|
455
|
+
// Preserve ring/spill notices; sandbox notes belong to the final renderer.
|
|
432
456
|
output: renderJobRead(
|
|
433
|
-
ringDelta(read.chunks), read.lossy, read.job.output.spillPaths ?? [],
|
|
457
|
+
ringDelta(read.chunks), read.lossy, read.job.output.spillPaths ?? [], undefined, escalationModes,
|
|
434
458
|
),
|
|
459
|
+
...(sandbox === undefined ? {} : { sandbox }),
|
|
435
460
|
}
|
|
436
461
|
}
|
|
437
462
|
registry.remove(attached.id, owner)
|
|
@@ -486,7 +511,16 @@ export function createBashToolModule({ executor, toolsModule, sandboxModule, llm
|
|
|
486
511
|
const policy = approvedMode === undefined
|
|
487
512
|
? standingPolicy
|
|
488
513
|
: { ...standingPolicy, mode: approvedMode }
|
|
489
|
-
|
|
514
|
+
// Optional read (issue #27): on hosts that provide workingDirectory
|
|
515
|
+
// this is the same ensure() seam the modern official tool-bash uses;
|
|
516
|
+
// on hosts without it, fall back to the session cwd exactly the way
|
|
517
|
+
// the rc.2-era official resolveWorkdir did (policy root ?? header).
|
|
518
|
+
const workingDirectory = ctx.get !== undefined ? ctx.get('workingDirectory') : undefined
|
|
519
|
+
const cwd = exec.agent === undefined
|
|
520
|
+
? undefined
|
|
521
|
+
: workingDirectory !== undefined
|
|
522
|
+
? await workingDirectory.ensure(exec.agent, exec.signal)
|
|
523
|
+
: (standingPolicy?.workspaceRoot ?? exec.agent?.session?.header?.cwd)
|
|
490
524
|
const workdir = resolveWorkdir(args.workdir, cwd)
|
|
491
525
|
const dshEnv = ctx.shellEnv.collect(exec)
|
|
492
526
|
const request = {
|
|
@@ -505,7 +539,15 @@ export function createBashToolModule({ executor, toolsModule, sandboxModule, llm
|
|
|
505
539
|
}
|
|
506
540
|
if (exec.signal.aborted) throw toolAborted(llmModule?.HarnessError)
|
|
507
541
|
const spec = executor.resolve({ ...request, onExpiry: 'none' })
|
|
508
|
-
|
|
542
|
+
// The job may still be preparing: expose the planned route, not a
|
|
543
|
+
// claim that the command has already run or been confined.
|
|
544
|
+
const route = typeof executor.gitBashRoute === 'function' ? executor.gitBashRoute(spec) : undefined
|
|
545
|
+
const sandbox = route === undefined ? undefined : {
|
|
546
|
+
mode: route.mode, denied: false,
|
|
547
|
+
...(route.unconfined ? { enforcement: 'unconfined' } : {}),
|
|
548
|
+
}
|
|
549
|
+
return { kind: 'background', jobId: startJob(jobs, args, exec, spec).id, cwd: spec.workdir,
|
|
550
|
+
...(sandbox === undefined ? {} : { sandbox }) }
|
|
509
551
|
}
|
|
510
552
|
if (jobs !== undefined && promote) {
|
|
511
553
|
const spec = executor.resolve({ ...request, onExpiry: 'none' })
|
|
@@ -534,9 +576,9 @@ export function createBashToolModule({ executor, toolsModule, sandboxModule, llm
|
|
|
534
576
|
render: (_args, value) => [{
|
|
535
577
|
type: 'text',
|
|
536
578
|
text: value.kind === 'background'
|
|
537
|
-
?
|
|
579
|
+
? renderBackground(value.jobId, value.sandbox)
|
|
538
580
|
: value.kind === 'promoted'
|
|
539
|
-
? renderPromoted(value)
|
|
581
|
+
? renderPromoted(value, escalationModes)
|
|
540
582
|
: renderResult(value, escalationModes),
|
|
541
583
|
}],
|
|
542
584
|
presentationMeta: (_args, value) => ({ cwd: value.cwd }),
|
|
@@ -564,9 +606,9 @@ export function createBashToolModule({ executor, toolsModule, sandboxModule, llm
|
|
|
564
606
|
render: (_args, value) => [{
|
|
565
607
|
type: 'text',
|
|
566
608
|
text: value.kind === 'background'
|
|
567
|
-
?
|
|
609
|
+
? renderBackground(value.jobId, value.sandbox)
|
|
568
610
|
: value.kind === 'promoted'
|
|
569
|
-
? renderPromoted(value)
|
|
611
|
+
? renderPromoted(value, escalationModes)
|
|
570
612
|
: renderResult(value, escalationModes),
|
|
571
613
|
}],
|
|
572
614
|
presentationMeta: (_args, value) => ({ cwd: value.cwd }),
|
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
|
-
|
|
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(
|
|
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
|
|
586
|
-
fastctxTools: valueOf(config.fastctxTools)
|
|
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)
|
|
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
|
}
|
|
@@ -612,15 +612,15 @@ const POSIX_DIRECTIVE_TEXT_STRICT = 'The working shell is Git for Windows bash:
|
|
|
612
612
|
* write paths into, so the sentence would be pure noise there. Cheap and
|
|
613
613
|
* honest: the tool list of the very assembly being rendered decides.
|
|
614
614
|
*/
|
|
615
|
-
const RUN_CODE_DIRECTIVE_TEXT = '
|
|
616
|
-
const
|
|
615
|
+
const RUN_CODE_DIRECTIVE_TEXT = ' In a run_code program, compatible plain MSYS path literals are translated on a best-effort basis; interpolated or tagged templates and uncertain syntax are not rewritten. For direct native Node fs/path APIs, prefer explicit Windows drive-letter or cwd-relative paths. Do not rely on literal rewriting for dynamic paths, shell command strings, routes or search data; tool path arguments are translated separately. Shell expansion is unavailable in program strings, so do not rely on $VARS.'
|
|
616
|
+
const RUN_CODE_DIRECTIVE_OFF_TEXT = ' In a run_code program, automatic path-literal rewriting is OFF. Direct native Node fs/path APIs require Windows drive-letter or cwd-relative paths, not MSYS /c/... strings. Tool path arguments are still translated separately; program strings do not perform shell expansion.'
|
|
617
617
|
|
|
618
618
|
/** The run_code sentence for this assembly, or '' when the tool is not offered. */
|
|
619
|
-
export function runCodeHintFor(tools) {
|
|
619
|
+
export function runCodeHintFor(tools, codePaths = true) {
|
|
620
620
|
if (!Array.isArray(tools)) return ''
|
|
621
621
|
for (const tool of tools) {
|
|
622
622
|
const name = tool && typeof tool.name === 'string' ? tool.name : ''
|
|
623
|
-
if (name === 'run_code') return RUN_CODE_DIRECTIVE_TEXT
|
|
623
|
+
if (name === 'run_code') return codePaths === true ? RUN_CODE_DIRECTIVE_TEXT : RUN_CODE_DIRECTIVE_OFF_TEXT
|
|
624
624
|
}
|
|
625
625
|
return ''
|
|
626
626
|
}
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
|
@@ -1155,15 +1221,15 @@ export function translatePathArguments(args, env) {
|
|
|
1155
1221
|
// sees it (see AGENTS.md 4c). The same mount table is therefore applied to the
|
|
1156
1222
|
// program's string literals — but ONLY when the whole program scans cleanly and
|
|
1157
1223
|
// a literal's ENTIRE content is an absolute MSYS path. Anything unclear — an
|
|
1158
|
-
// unterminated literal
|
|
1159
|
-
//
|
|
1224
|
+
// unterminated literal or a regex we cannot place — turns the whole rewrite
|
|
1225
|
+
// into a no-op. Interpolated/tagged templates are skipped as data. A partial guess would
|
|
1160
1226
|
// be far worse than an untranslated one, and this layer never guesses.
|
|
1161
1227
|
//
|
|
1162
1228
|
// Deliberately NOT translated: shell-style '$VAR/x' (a JS literal is data, not
|
|
1163
1229
|
// a shell word) and anything carrying a scheme ('http://...').
|
|
1164
1230
|
|
|
1165
1231
|
/** Source text allowed inside a rewritten literal: no escapes, no dollar sign. */
|
|
1166
|
-
const CODE_LITERAL_SAFE = /^[
|
|
1232
|
+
const CODE_LITERAL_SAFE = /^[\p{L}\p{M}\p{N}._@+~#\-/ ]+$/u
|
|
1167
1233
|
|
|
1168
1234
|
/** Previous significant character that lets a '/' start a regex literal. */
|
|
1169
1235
|
function regexCanStart(previous) {
|
|
@@ -1309,7 +1375,8 @@ export function scanCodeLiterals(code) {
|
|
|
1309
1375
|
const next = code[i + 1]
|
|
1310
1376
|
if (c === '/' && next === '/') {
|
|
1311
1377
|
const nl = code.indexOf('\n', i)
|
|
1312
|
-
|
|
1378
|
+
// A line comment may legally terminate at EOF (outside interpolation).
|
|
1379
|
+
if (nl === -1) break
|
|
1313
1380
|
i = nl
|
|
1314
1381
|
continue
|
|
1315
1382
|
}
|
|
@@ -1330,7 +1397,12 @@ export function scanCodeLiterals(code) {
|
|
|
1330
1397
|
if (c === '`') {
|
|
1331
1398
|
const template = skipTemplateLiteral(code, i)
|
|
1332
1399
|
if (template === null) return null
|
|
1333
|
-
|
|
1400
|
+
// A preceding expression can be a tag (String.raw, fn(), obj[key], ...).
|
|
1401
|
+
// Tags own raw/cooked semantics: never rewrite their template data.
|
|
1402
|
+
// Contextual words can also be identifiers/tags; skip rather than guess.
|
|
1403
|
+
if (template.plain && regexCanStart(last) && !['of', 'await', 'yield'].includes(last)) {
|
|
1404
|
+
spans.push({ start: i + 1, end: template.end - 1 })
|
|
1405
|
+
}
|
|
1334
1406
|
i = template.end
|
|
1335
1407
|
last = 'x'
|
|
1336
1408
|
continue
|
|
@@ -1489,7 +1561,12 @@ export function translateGlobArguments(args, env) {
|
|
|
1489
1561
|
// captured and restored on disposal (reload/update/uninstall), so removing
|
|
1490
1562
|
// this plugin returns the sidebar to what it was before.
|
|
1491
1563
|
|
|
1492
|
-
|
|
1564
|
+
// The settings namespace is the row id in dsh-better-sidebar's own bundle
|
|
1565
|
+
// patch (`- id: better-sidebar`; verified against the published 0.25.0
|
|
1566
|
+
// tarball) — NOT the npm package name. Using the package name made
|
|
1567
|
+
// settings.describe() never match and every takeover silently no-op with one
|
|
1568
|
+
// "unavailable" log line (issue #25, Bug 3).
|
|
1569
|
+
const SIDEBAR_NS = 'better-sidebar'
|
|
1493
1570
|
const SIDEBAR_TRIES = 12
|
|
1494
1571
|
const SIDEBAR_RETRY_MS = 1500
|
|
1495
1572
|
|
|
@@ -2179,7 +2256,7 @@ export async function apply(ctx, config = {}) {
|
|
|
2179
2256
|
// v0.20.1: the run_code sentence rides the SAME context entry, but
|
|
2180
2257
|
// only when this assembly actually offers the tool (see
|
|
2181
2258
|
// runCodeHintFor) — every other mode stays quiet.
|
|
2182
|
-
const hint = runCodeHintFor(assembly.tools)
|
|
2259
|
+
const hint = runCodeHintFor(assembly.tools, dialect.codePaths === true)
|
|
2183
2260
|
if (hint !== '') {
|
|
2184
2261
|
for (const assembled of Array.isArray(assembly.contexts) ? assembly.contexts : []) {
|
|
2185
2262
|
if (assembled && assembled.name === 'gitbash-shell:posix-paths' && typeof assembled.text === 'string') {
|
package/src/inline-tools.js
CHANGED
|
@@ -285,6 +285,18 @@ 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
|
+
// An INACTIVE fiber resolves its await() without ever running apply()
|
|
289
|
+
// (issue #27): awaiting the plugin proves nothing, so read the registry
|
|
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).
|
|
294
|
+
const registeredNames = scope.ctx.get !== undefined
|
|
295
|
+
? ((scope.ctx.get('tools')?.schemas?.(agent) ?? []).map((schema) => schema && typeof schema.name === 'string' ? schema.name : ''))
|
|
296
|
+
: []
|
|
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)')
|
|
299
|
+
}
|
|
288
300
|
retained = true
|
|
289
301
|
log('own bash tool activated for this agent (Git Bash execution; official pwsh tool real but hidden by default)')
|
|
290
302
|
} catch (error) {
|
package/src/shell.js
CHANGED
|
@@ -10,25 +10,23 @@
|
|
|
10
10
|
* and hands it to the plugin's OWN bash tool (src/bash-tool.js). The class
|
|
11
11
|
* body is unchanged from its executor-row era:
|
|
12
12
|
*
|
|
13
|
-
* - extends @deepseek-ai/dsh-bash-sandbox
|
|
14
|
-
*
|
|
15
|
-
* denial, and runner-failure semantics of the shipped stack;
|
|
13
|
+
* - extends @deepseek-ai/dsh-bash-sandbox to inherit policy, denial and
|
|
14
|
+
* lifecycle semantics EXCEPT the explicit Windows unconfined route below;
|
|
16
15
|
* - overrides `confine` so the inner argv is [git-bash, -c, command];
|
|
17
16
|
* - overrides run/start ONLY for danger-full-access (the parent's
|
|
18
17
|
* full-access branch calls LocalBashExecutor.run, which hardcodes the
|
|
19
18
|
* bare `bash` name), routing through runArgv/startArgv with the same
|
|
20
19
|
* argv as the confined branch;
|
|
21
20
|
* - on Windows ALSO overrides the confined branch itself (issue #1):
|
|
22
|
-
* MSYS2
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* notice. The fs-tool sandbox policy still applies to file tools.
|
|
21
|
+
* historical MSYS2/restricted-token tests failed during msys-2.0.dll
|
|
22
|
+
* cygheap/signal-pipe initialization (Win32 error 5 / 0xC0000142;
|
|
23
|
+
* restricting-SID object permissions, CHANGELOG v0.13.2). No working
|
|
24
|
+
* confined Git Bash implementation is provided here; this is not a claim
|
|
25
|
+
* that every future runtime/isolation design is impossible. Windows calls
|
|
26
|
+
* retain the explicit UNCONFINED route, labelled on the handle/result and
|
|
27
|
+
* in model-facing foreground/background text, plus a one-time notice.
|
|
28
|
+
* Official file-tool write/edit policy remains separate; it does not
|
|
29
|
+
* constrain shell code and its denials must never be bypassed through shell.
|
|
32
30
|
*
|
|
33
31
|
* Environment inheritance: bash.exe is spawned as a direct child of the host
|
|
34
32
|
* process (never through the git-bash.exe login launcher), so it inherits the
|
|
@@ -262,6 +260,8 @@ export class GitBashSandboxExecutor extends SandboxBashExecutor {
|
|
|
262
260
|
* @returns the same handle, with `result()` mapped.
|
|
263
261
|
*/
|
|
264
262
|
static decorateExecution(ex, sandbox) {
|
|
263
|
+
// Background reads/outcomes inspect the handle before result() settles.
|
|
264
|
+
ex.sandbox = sandbox
|
|
265
265
|
const base = ex.result.bind(ex)
|
|
266
266
|
let decorated
|
|
267
267
|
ex.result = () => {
|
|
@@ -407,7 +407,7 @@ export class GitBashSandboxExecutor extends SandboxBashExecutor {
|
|
|
407
407
|
warnConfinedUnconfined(mode) {
|
|
408
408
|
if (this._warnedUnconfined) return
|
|
409
409
|
this._warnedUnconfined = true
|
|
410
|
-
console.log(TAG + ' Windows:
|
|
410
|
+
console.log(TAG + ' Windows: confined Git Bash is not provided by this restricted-token backend (issue #1). Requested mode ' + mode + ' will use an UNCONFINED Git Bash route: shell file-write restrictions are NOT enforced. Official file-tool write/edit policy is separate; never bypass its denial through shell.')
|
|
411
411
|
}
|
|
412
412
|
}
|
|
413
413
|
|