dsh-gitbash-shell 0.39.2 → 0.39.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 +62 -0
- package/README.md +38 -14
- package/README_EN.md +54 -18
- 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/index.js +22 -11
- package/src/inline-tools.js +12 -1
- package/src/shell.js +14 -14
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,68 @@
|
|
|
3
3
|
> 倒序排列,新版本条目在最上面。条目格式:`## vX.Y.Z — YYYY-MM-DD` + 类型(feat / fix / docs / chore)+ 要点 + 相关链接。
|
|
4
4
|
> 纪律见 AGENTS.md「变更记录纪律」:发版前先更新本文件并随版本提交;事故复盘、复现与真机验证记录也记在这里。
|
|
5
5
|
|
|
6
|
+
## v0.39.3 — 2026-10-14
|
|
7
|
+
|
|
8
|
+
**类型**:fix(自有 bash 工具的宿主服务兼容 #27、MSYS 参数/编码提示、源码路径边界与 unconfined 可观测性) / test / docs。
|
|
9
|
+
|
|
10
|
+
### issue #27:自有 bash 工具在无 `workingDirectory` 服务的宿主上永不注册
|
|
11
|
+
|
|
12
|
+
- **根因(route B 镜像的契约盲区)**:自有 bash 工具镜像的是新版宿主(`0.2.1-alpha.x`)官方 tool-bash
|
|
13
|
+
的 `inject` 列表(含 `workingDirectory`),而兼容矩阵声明的 `0.2.0-rc.2` 上**没有该服务**——cordis 对
|
|
14
|
+
无法满足的 inject 让模块 fiber **永久 INACTIVE**:`apply()` 从不执行、工具永不注册、零错误日志,且
|
|
15
|
+
inline-tools 的激活日志照打(`await` 一个 INACTIVE fiber 会正常 resolve)——**假阳性成功**。
|
|
16
|
+
与 v0.24.0 settingsScope、v0.24.3 静态 peer import 同一失败类(静默失效、宿主日志全绿)。
|
|
17
|
+
- **修复**:① `inject` 收敛为 `['tools', 'systemPrompt', 'shellEnv']`(服务最小面,`workingDirectory`
|
|
18
|
+
改为使用点 `ctx.get()` 可选读取);② cwd 接缝双向兼容——有 `workingDirectory` 的宿主走
|
|
19
|
+
`ensure()`(与新版官方一致),没有的宿主退回 rc.2 时代官方 `resolveWorkdir` 的同一条接缝
|
|
20
|
+
(`policy.workspaceRoot ?? session.header.cwd`),**不再落到宿主进程目录**;③ 激活日志必须经
|
|
21
|
+
`tools.schemas(agent)` 回读确认 `bash` 真正在册,不在册打显式失败行(点名 INACTIVE 可能性 +
|
|
22
|
+
请携带 dsh 版本报告)——**消除假阳性**。
|
|
23
|
+
- **教训(§4l 追记⑧)**:镜像宿主模块时,`inject` 面是**契约面**——必须取兼容矩阵内**最老宿主**的
|
|
24
|
+
形状,或一律可选读取;「宿主新版本有」不等于「矩阵内都有」。
|
|
25
|
+
- 报告者 largoalladonzella 提供了完整根因链(asar 字节级取证 + cordis `_refresh`/`_setEpoch` 源码 +
|
|
26
|
+
官方两版 inject 对照)与本机验证补丁;本仓库按其建议实现并补真断言。
|
|
27
|
+
- 新增回归:无 `workingDirectory` 的最小 ctx 上 `apply()` 照常注册 + cwd 回退正确;有服务的宿主
|
|
28
|
+
`ensure()` 接缝不变;源码断言锁死硬 inject 列表与「回读门控激活日志」。本地全量 **168/168**(+1
|
|
29
|
+
win32-only skip)。
|
|
30
|
+
|
|
31
|
+
### PR #24:超时转后台的独立 sandbox 警示
|
|
32
|
+
|
|
33
|
+
- promoted 返回值显式携带可选 sandbox facts,输出 schema 保持闭合;defineTool 与 JSON-schema 回退共用此契约。
|
|
34
|
+
- `renderPromoted` 自行追加 sandbox notes,不再依赖 output 预先含警示;promotion 输出保留丢失/spill 提示,
|
|
35
|
+
避免重复追加 sandbox 警示。无 sandbox/full-access 的原反馈保持不变,denied/runnerFailed 分支继续保留。
|
|
36
|
+
- 纯回归改用不含警示的原始文本,覆盖空输出、full-access、拒绝与 runner failure。
|
|
37
|
+
新增实际 ToolRuntime→私有执行器→内存 provider 的 promotion 回归(Jobs wait/read 为受控桩),
|
|
38
|
+
验证 RO/WW 警示各出现一次、full 无警示、DTO 与最终模型文本分离、丢失/spill 提示及 root 服务不变。
|
|
39
|
+
- 验证:普通完整回归 **166/166**、实际安装Core完整preview **18/18**,均exit0,无skip/cancel;
|
|
40
|
+
语法与diff检查通过。不等同于生产GUI或真实Jobs时钟验收。
|
|
41
|
+
|
|
42
|
+
### MSYS 兼容边界尝试修复
|
|
43
|
+
|
|
44
|
+
- **范围**:从 [`6b26ded`](https://github.com/KouzakiUmi/dsh-gitbash-shell/commit/6b26ded6cc3a2a6113579149286047629b433c48)
|
|
45
|
+
开出 `fix/msys-compatibility-boundaries`;不改 root PowerShell、market patch、生产 Profile、
|
|
46
|
+
安全默认或版本号。原生参数转换/混合编码/Windows shell confinement 不能在这层安全地普遍修好,
|
|
47
|
+
因此修工具提示与可观测性,不全局 EXCL=*、不猜码、不将警示冒充沙箱修复。
|
|
48
|
+
- **源码路径**:run_code 的完整 Unicode 字母/组合字符/数字和含 `#` 文件名字面量走共享挂载表;
|
|
49
|
+
合法 EOF 行注释不再让整份重写弃权;tagged templates(含 String.raw、调用/索引 tag)保持原样,
|
|
50
|
+
避免 raw 设备路径被错误转义;of/await/yield 等上下文词可作 tag 时保守不改。
|
|
51
|
+
未闭合/不确定正则与除法仍整份 no-op,未扩展插值或动态表达式猜测。
|
|
52
|
+
词法层仍无法识别非路径数据的业务用途,提示明确建议直接 Node fs/path 用 native/cwd-relative 路径。
|
|
53
|
+
- **提示门控**:真实 prompt listener 读取 live codePaths;OFF 不再承诺程序字符串会被翻译,
|
|
54
|
+
ON 改为 best-effort 并指出模板、动态/非路径数据限制。关闭 codePaths 不改变工具路径字段翻译。
|
|
55
|
+
- **原生互操作**:登记的 bash 描述无条件说明 `/all`/`/c` 转换、双斜杠/单命令 EXCL 前缀及其
|
|
56
|
+
对 `/c/...` 的影响;检查预期效果而非只看 exit0。说明 UTF-8 捕获与 CP936/GBK 不匹配,
|
|
57
|
+
显式转码须在字节捕获前且保持退出状态;locale 与全流猜码不是普遍修复。
|
|
58
|
+
- **unconfined 可见性**:执行句柄在等待 result() 前带 sandbox facts;前台正文、后台 outcome/
|
|
59
|
+
增量嵌入与超时转后台显示警示。后台登记输出联合新增可选 sandbox facts,指出计划执行边界,
|
|
60
|
+
不把未启动的 job 宣称为已经执行。保留原请求 mode,不把 unconfined 当 denial/escalation,
|
|
61
|
+
并明确不得通过 shell 绕过文件工具策略拒绝。双语说明区分文件读能力与 write/edit 围栏,
|
|
62
|
+
删除“所有未来版本都无解”的过度结论。
|
|
63
|
+
- **验证**:新增9项兼容回归和1项后台登记回归,普通全量 **166/166**;新增3项实际 Core
|
|
64
|
+
ToolRuntime/执行句柄/run_code transport 回归,完整无头 preview **17/17**。旧护栏保留;打包 dry-run、语法和 diff
|
|
65
|
+
检查通过。新增 preview 使用实际宿主 API 与内存 subprocess,已有 preview 保留真实 Git Bash/
|
|
66
|
+
PowerShell 进程验收;不等同于生产 GUI、真实 PTC 引擎或新 confinement 方案验证。
|
|
67
|
+
|
|
6
68
|
## v0.39.2 — 2026-10-13
|
|
7
69
|
|
|
8
70
|
**类型**:fix(route B 工具/预设生命周期、动态配置与客户端回归) / test。
|
package/README.md
CHANGED
|
@@ -118,20 +118,19 @@ bundle patch(`cordis.patch.yml`)是**纯新增**(v0.39.0 起):只插入本插件
|
|
|
118
118
|
**Windows 宿主上的事实**:`read-only` / `workspace-write` 权限预设下的 **shell 命令**,
|
|
119
119
|
实际以**不受限(unconfined)方式执行**:
|
|
120
120
|
|
|
121
|
-
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
shell 能写出工作区、文件工具不能,**两者的边界并不一致**。
|
|
121
|
+
- **为什么**:已验证的 Git Bash/MSYS 组合在现有 restricted-token 后端下初始化失败,
|
|
122
|
+
历史复现为 `msys-2.0.dll` 的 cygheap/signal-pipe 对象权限与 restricting-SID
|
|
123
|
+
二次写检查不兼容(Win32 error 5 / 0xC0000142)。当前没有提供可用的受限 Git Bash
|
|
124
|
+
实现,不声称所有未来运行时或隔离方案都无解。本插件保留**如实标注的绕过**;
|
|
125
|
+
没有静默回退 pwsh/cmd/WSL,本次兼容修复也没有恢复 shell 写入围栏。
|
|
126
|
+
- **如何标注**:结果元数据保留
|
|
127
|
+
`sandbox: { mode: "<请求的模式>", denied: false, enforcement: "unconfined" }`;
|
|
128
|
+
工具描述、前台正文、后台登记及结果摘要、超时转后台反馈都明确警示 shell 写限制未生效。
|
|
129
|
+
后台登记标明的是计划采用的执行边界,不代表命令已经运行;实例首次调用另有日志。
|
|
130
|
+
- **文件工具是另一层**:官方 fs-tool policy 对 write/edit 保留策略检查:
|
|
131
|
+
`read-only` 拒绝修改,`workspace-write` 只允许声明的工作区和平台临时可写根。
|
|
132
|
+
read/glob/grep 等读取并非一律限制在工作区内。shell 进程不受这些工具检查约束,
|
|
133
|
+
**两者的边界并不一致**;绝不能用 shell 绕过文件工具已返回的策略拒绝。
|
|
135
134
|
- **可观察的对照**(相对 pwsh-sandbox 时代):会话进程的完整性级别不再被压低
|
|
136
135
|
(Low → Medium);`$env:TEMP` 不再被重定向到 per-session 隔离目录。
|
|
137
136
|
|
|
@@ -396,6 +395,31 @@ PTC/创造及用户自建模式一律覆盖。
|
|
|
396
395
|
都不再改写,文件工具收 Windows 路径。**bash 始终是 Git Bash,不受此开关影响**——它只决定
|
|
397
396
|
跨工具的路径方言。
|
|
398
397
|
|
|
398
|
+
## 原生 Windows 程序与 run_code 的兼容边界
|
|
399
|
+
|
|
400
|
+
工具描述始终警示下面的参数、编码和安全限制;这些限制不随路径方言关闭而消失。
|
|
401
|
+
插件**不会自动改写 command、全局关闭 MSYS 参数转换或猜测输出编码**。
|
|
402
|
+
|
|
403
|
+
- **斜杠开关**:MSYS 在启动原生 Windows 程序时可能把 `/all`、`/c` 当路径。
|
|
404
|
+
本机已验证 `ipconfig //all`、`cmd //c` 或单命令
|
|
405
|
+
`MSYS2_ARG_CONV_EXCL='/all' ipconfig /all` 可保留开关。
|
|
406
|
+
排除值是前缀:排除 `/c` 也会影响真正的 `/c/...` 路径,该次原生调用应明确用
|
|
407
|
+
Windows drive-letter 路径。不要全局设置 `MSYS2_ARG_CONV_EXCL='*'`。
|
|
408
|
+
`cmd /c` 被转换后可能 exit0 却没有执行所需命令,应检查预期效果而非只看退出码。
|
|
409
|
+
- **输出编码**:宿主按 UTF-8 解码;原生 Windows 程序可能输出 CP936/GBK 或其他代码页。
|
|
410
|
+
先使用程序自己的 UTF-8 输出选项,或在捕获前按已知编码显式转换字节,保持原退出状态。
|
|
411
|
+
`LANG/LC_ALL` 不保证改变原生程序的代码页。不要全流默认 GBK 或依替换字符数自动猜码:
|
|
412
|
+
同一 Bash 调用可能混合 UTF-8/GBK,已有乱码字符串也不能可靠恢复原字节。
|
|
413
|
+
- **run_code**:`codePaths` 默认开,与 `posixPaths`/委托 gate 同时生效时,完整、可判定的
|
|
414
|
+
普通路径字面量按共享挂载表尽力转换,包括中文/组合字符/空格/`#` 文件名。
|
|
415
|
+
EOF 行注释不再阻止转换;tagged template(包括 String.raw)保持原样,避免破坏 raw/tag 语义。
|
|
416
|
+
插值模板、转义路径、不确定的正则/除法语法仍可能不改;不确定时整份 no-op。
|
|
417
|
+
直接调用 Node fs/path 时优先明确的 Windows drive-letter 或 cwd-relative 路径;
|
|
418
|
+
工具 path/file_path 字段仍走工具参数翻译。词法扫描不能识别路由、搜索数据或 shell
|
|
419
|
+
command 的用途;此类字符串不要依赖自动重写,必要时关闭 `codePaths`(提示同步标为 OFF)。
|
|
420
|
+
- **沙箱**:参见上方安全章节。警示可见性得到修复,但 Windows shell 的 unconfined
|
|
421
|
+
取舍未改变,文件工具的策略拒绝绝不授权 shell 重试。
|
|
422
|
+
|
|
399
423
|
## 与 dsh-better-sidebar 联动
|
|
400
424
|
|
|
401
425
|
装了 [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
|
@@ -93,24 +93,26 @@ restricted tokens, workspace fencing, denial classification, identical to
|
|
|
93
93
|
**On Windows**, shell commands under the `read-only` / `workspace-write`
|
|
94
94
|
permission presets actually run **UNCONFINED**:
|
|
95
95
|
|
|
96
|
-
- **Why**:
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
- **
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
the workspace
|
|
96
|
+
- **Why**: tested Git Bash/MSYS combinations failed to initialize under the
|
|
97
|
+
existing restricted-token backend. Historical reproduction found cygheap/
|
|
98
|
+
signal-pipe object permissions incompatible with the restricting-SID write
|
|
99
|
+
check (Win32 error 5 / 0xC0000142). No working confined Git Bash implementation
|
|
100
|
+
is provided here; this is not a claim that every future runtime or isolation
|
|
101
|
+
design is impossible. The plugin retains its **labelled bypass**, with no
|
|
102
|
+
silent fallback to pwsh/cmd/WSL. Compatibility fixes do not restore confinement.
|
|
103
|
+
- **How it is labelled**: results retain
|
|
104
|
+
`sandbox: { mode: "<requested mode>", denied: false, enforcement: "unconfined" }`.
|
|
105
|
+
The tool description, foreground text, initial background registration,
|
|
106
|
+
background outcome and timeout-promotion feedback warn that shell write
|
|
107
|
+
restrictions are not enforced. Initial registration describes the planned
|
|
108
|
+
boundary, not a claim that the command has already run; there is also a
|
|
109
|
+
one-time notice per executor instance.
|
|
110
|
+
- **Official file tools have a separate policy**: write/edit still reject
|
|
111
|
+
mutations under `read-only`; `workspace-write` checks the declared workspace
|
|
112
|
+
and platform writable temp roots. Reads such as read/glob/grep are not all
|
|
113
|
+
limited to the workspace. Shell processes are not governed by these tool
|
|
114
|
+
checks — **the two boundaries differ**. Never bypass a file-tool policy
|
|
115
|
+
denial through shell.
|
|
114
116
|
- **Observable differences** vs the pwsh-sandbox era: the session process's
|
|
115
117
|
integrity level is no longer lowered (Low → Medium), and `$env:TEMP` is no
|
|
116
118
|
longer redirected to a per-session directory.
|
|
@@ -125,6 +127,40 @@ official PowerShell executor was never touched by this plugin. The
|
|
|
125
127
|
> The full analysis (the 0.6.0–0.13.1 failure history and the community's two
|
|
126
128
|
> camps) is in the CHANGELOG under v0.13.2 and in the `src/shell.js` header.
|
|
127
129
|
|
|
130
|
+
## Native Windows and run_code compatibility boundaries
|
|
131
|
+
|
|
132
|
+
The tool description always warns about these argument, encoding and security
|
|
133
|
+
boundaries, independently of the path-dialect switch. The plugin does not
|
|
134
|
+
silently rewrite command source, globally disable argument conversion or guess
|
|
135
|
+
output encodings.
|
|
136
|
+
|
|
137
|
+
- **Slash options**: MSYS can treat native Windows `/all` and `/c` as paths.
|
|
138
|
+
Tested alternatives are `ipconfig //all`, `cmd //c`, or a per-command
|
|
139
|
+
`MSYS2_ARG_CONV_EXCL='/all' ipconfig /all`. Exclusions match prefixes: excluding
|
|
140
|
+
`/c` also excludes `/c/...` file paths, so explicitly use native drive-letter
|
|
141
|
+
paths for those arguments. Do not set `MSYS2_ARG_CONV_EXCL='*'` globally.
|
|
142
|
+
A converted `cmd /c` may exit0 without performing the intended action; verify
|
|
143
|
+
the expected effect, not only the exit code.
|
|
144
|
+
- **Output encodings**: the host decodes UTF-8. Native programs may emit CP936/GBK
|
|
145
|
+
or another code page. Prefer a program's UTF-8 option, or explicitly convert
|
|
146
|
+
known-encoding bytes before capture while preserving the original exit state.
|
|
147
|
+
Locale variables do not guarantee native UTF-8. Do not decode all Bash output
|
|
148
|
+
as GBK or infer encoding from replacement-character counts: one call can mix
|
|
149
|
+
UTF-8 and GBK, and an already-corrupted string cannot recover the original bytes.
|
|
150
|
+
- **run_code**: `codePaths` defaults ON and follows the total/delegate dialect
|
|
151
|
+
gates. Complete compatible plain path literals are rewritten best-effort with
|
|
152
|
+
the shared mount table, including Unicode letters/marks, spaces and `#` names.
|
|
153
|
+
Legal EOF line comments no longer prevent rewriting; tagged templates (such
|
|
154
|
+
as String.raw) retain their data and raw semantics. Interpolation, escapes,
|
|
155
|
+
uncertain regex/division syntax may remain unchanged; uncertainty abandons the
|
|
156
|
+
entire rewrite. Prefer native drive-letter or cwd-relative paths for direct
|
|
157
|
+
Node fs/path APIs; typed tool path arguments remain translated separately.
|
|
158
|
+
Lexical scanning cannot distinguish routes/search data/shell command strings
|
|
159
|
+
from paths. Do not rely on rewriting those values; disable `codePaths` when
|
|
160
|
+
needed (the model hint now accurately states OFF).
|
|
161
|
+
- **Security**: warnings are now visible, but Windows unconfined behavior is
|
|
162
|
+
unchanged. A file-tool policy denial never authorizes a shell retry.
|
|
163
|
+
|
|
128
164
|
## Config
|
|
129
165
|
|
|
130
166
|
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.3",
|
|
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.3",
|
|
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/index.js
CHANGED
|
@@ -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
|
}
|
|
@@ -1155,15 +1155,15 @@ export function translatePathArguments(args, env) {
|
|
|
1155
1155
|
// sees it (see AGENTS.md 4c). The same mount table is therefore applied to the
|
|
1156
1156
|
// program's string literals — but ONLY when the whole program scans cleanly and
|
|
1157
1157
|
// a literal's ENTIRE content is an absolute MSYS path. Anything unclear — an
|
|
1158
|
-
// unterminated literal
|
|
1159
|
-
//
|
|
1158
|
+
// unterminated literal or a regex we cannot place — turns the whole rewrite
|
|
1159
|
+
// into a no-op. Interpolated/tagged templates are skipped as data. A partial guess would
|
|
1160
1160
|
// be far worse than an untranslated one, and this layer never guesses.
|
|
1161
1161
|
//
|
|
1162
1162
|
// Deliberately NOT translated: shell-style '$VAR/x' (a JS literal is data, not
|
|
1163
1163
|
// a shell word) and anything carrying a scheme ('http://...').
|
|
1164
1164
|
|
|
1165
1165
|
/** Source text allowed inside a rewritten literal: no escapes, no dollar sign. */
|
|
1166
|
-
const CODE_LITERAL_SAFE = /^[
|
|
1166
|
+
const CODE_LITERAL_SAFE = /^[\p{L}\p{M}\p{N}._@+~#\-/ ]+$/u
|
|
1167
1167
|
|
|
1168
1168
|
/** Previous significant character that lets a '/' start a regex literal. */
|
|
1169
1169
|
function regexCanStart(previous) {
|
|
@@ -1309,7 +1309,8 @@ export function scanCodeLiterals(code) {
|
|
|
1309
1309
|
const next = code[i + 1]
|
|
1310
1310
|
if (c === '/' && next === '/') {
|
|
1311
1311
|
const nl = code.indexOf('\n', i)
|
|
1312
|
-
|
|
1312
|
+
// A line comment may legally terminate at EOF (outside interpolation).
|
|
1313
|
+
if (nl === -1) break
|
|
1313
1314
|
i = nl
|
|
1314
1315
|
continue
|
|
1315
1316
|
}
|
|
@@ -1330,7 +1331,12 @@ export function scanCodeLiterals(code) {
|
|
|
1330
1331
|
if (c === '`') {
|
|
1331
1332
|
const template = skipTemplateLiteral(code, i)
|
|
1332
1333
|
if (template === null) return null
|
|
1333
|
-
|
|
1334
|
+
// A preceding expression can be a tag (String.raw, fn(), obj[key], ...).
|
|
1335
|
+
// Tags own raw/cooked semantics: never rewrite their template data.
|
|
1336
|
+
// Contextual words can also be identifiers/tags; skip rather than guess.
|
|
1337
|
+
if (template.plain && regexCanStart(last) && !['of', 'await', 'yield'].includes(last)) {
|
|
1338
|
+
spans.push({ start: i + 1, end: template.end - 1 })
|
|
1339
|
+
}
|
|
1334
1340
|
i = template.end
|
|
1335
1341
|
last = 'x'
|
|
1336
1342
|
continue
|
|
@@ -1489,7 +1495,12 @@ export function translateGlobArguments(args, env) {
|
|
|
1489
1495
|
// captured and restored on disposal (reload/update/uninstall), so removing
|
|
1490
1496
|
// this plugin returns the sidebar to what it was before.
|
|
1491
1497
|
|
|
1492
|
-
|
|
1498
|
+
// The settings namespace is the row id in dsh-better-sidebar's own bundle
|
|
1499
|
+
// patch (`- id: better-sidebar`; verified against the published 0.25.0
|
|
1500
|
+
// tarball) — NOT the npm package name. Using the package name made
|
|
1501
|
+
// settings.describe() never match and every takeover silently no-op with one
|
|
1502
|
+
// "unavailable" log line (issue #25, Bug 3).
|
|
1503
|
+
const SIDEBAR_NS = 'better-sidebar'
|
|
1493
1504
|
const SIDEBAR_TRIES = 12
|
|
1494
1505
|
const SIDEBAR_RETRY_MS = 1500
|
|
1495
1506
|
|
|
@@ -2179,7 +2190,7 @@ export async function apply(ctx, config = {}) {
|
|
|
2179
2190
|
// v0.20.1: the run_code sentence rides the SAME context entry, but
|
|
2180
2191
|
// only when this assembly actually offers the tool (see
|
|
2181
2192
|
// runCodeHintFor) — every other mode stays quiet.
|
|
2182
|
-
const hint = runCodeHintFor(assembly.tools)
|
|
2193
|
+
const hint = runCodeHintFor(assembly.tools, dialect.codePaths === true)
|
|
2183
2194
|
if (hint !== '') {
|
|
2184
2195
|
for (const assembled of Array.isArray(assembly.contexts) ? assembly.contexts : []) {
|
|
2185
2196
|
if (assembled && assembled.name === 'gitbash-shell:posix-paths' && typeof assembled.text === 'string') {
|
package/src/inline-tools.js
CHANGED
|
@@ -286,7 +286,18 @@ export function mountInlineTools(ctx, deps) {
|
|
|
286
286
|
await untilDisposed(fiber, state.controller.signal)
|
|
287
287
|
if (!isLive(agent, state)) return
|
|
288
288
|
retained = true
|
|
289
|
-
|
|
289
|
+
// An INACTIVE fiber resolves its await() without ever running apply()
|
|
290
|
+
// (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.
|
|
293
|
+
const registeredNames = scope.ctx.get !== undefined
|
|
294
|
+
? ((scope.ctx.get('tools')?.schemas?.(agent) ?? []).map((schema) => schema && typeof schema.name === 'string' ? schema.name : ''))
|
|
295
|
+
: []
|
|
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)')
|
|
300
|
+
}
|
|
290
301
|
} catch (error) {
|
|
291
302
|
if (isLive(agent, state)) log(`own bash tool activation failed for this agent: ${error?.message ?? error}`)
|
|
292
303
|
} finally {
|
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
|
|