dsh-win-multi-bash 0.3.0 → 0.3.1
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/README.i18n.yaml +7 -8
- package/README.md +7 -1
- package/README.zh.md +7 -1
- package/lib/tool-bash/types/factory.js +43 -4
- package/package.json +1 -1
package/README.i18n.yaml
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
# Bilingual-pair consistency record
|
|
2
|
-
#
|
|
3
|
-
# languages carry equal authority;
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
|
|
7
|
-
README.md:
|
|
8
|
-
README.zh.md: 57860688aece6e36804587bca488b6dcbf201cce
|
|
1
|
+
# Bilingual-pair consistency record for this package: the git blob hash of each
|
|
2
|
+
# side as of the last confirmed-consistent state (whole-file, unlike the
|
|
3
|
+
# deepseek-harness per-section format). Both languages carry equal authority;
|
|
4
|
+
# after editing either side, bring the other along and re-record both hashes:
|
|
5
|
+
# git hash-object README.md README.zh.md
|
|
6
|
+
README.md: ccee00c06fbb2956f5a0559b2b74710e4d4eed55
|
|
7
|
+
README.zh.md: bfdcd780500df1a9bd67e9e07aece381cd52e0fd
|
package/README.md
CHANGED
|
@@ -70,7 +70,9 @@ wsl.exe -d Ubuntu-24.04 -e bash -c "command -v bwrap && bwrap --version" # ver
|
|
|
70
70
|
|
|
71
71
|
## Tool prompts (model-facing descriptions)
|
|
72
72
|
|
|
73
|
-
The `git_bash` / `wsl_bash` tool descriptions are deliberately concise and mirror the official `tool-pwsh` skeleton: a fresh shell per call, the dialect's paths/env form, `[exit code: N]` markers, `$DSH_*` environment facts, sandbox behavior, output truncation, background jobs, and the escalation contract. The longer dialect notes (MSYS path rewriting, WSL base64 payloads) live in this README rather than in the model-facing text.
|
|
73
|
+
The `git_bash` / `wsl_bash` tool descriptions are deliberately concise and mirror the official `tool-pwsh` skeleton: a fresh shell per call, the dialect's paths/env form, `[exit code: N]` markers, `$DSH_*` environment facts, sandbox behavior, output truncation, delete/move target verification, the unset-variable `${VAR:?}` guard, background jobs, and the escalation contract. The longer dialect notes (MSYS path rewriting, WSL base64 payloads) live in this README rather than in the model-facing text.
|
|
74
|
+
|
|
75
|
+
Both tools run `bash -c`, so that safety guidance is worded as in `tool-bash`, not as in `tool-pwsh`: `$HOME` is an ordinary assignable variable in bash, so pwsh's "do not assign to automatic variables" sentence would be wrong here — the bash guard for a computed path is the `${VAR:?}` form above, which makes an unset variable fail instead of silently expanding to an empty string.
|
|
74
76
|
|
|
75
77
|
`git_bash`'s description additionally carries a path-format hint:
|
|
76
78
|
|
|
@@ -78,6 +80,10 @@ The `git_bash` / `wsl_bash` tool descriptions are deliberately concise and mirro
|
|
|
78
80
|
|
|
79
81
|
So when a command prints an MSYS path (e.g. `/d/WorkSpace/foo`), convert it to its Windows form (`D:\WorkSpace\foo`) before handing it to dsh's file tools; inside the bash command itself, MSYS paths are what the shell expects.
|
|
80
82
|
|
|
83
|
+
**`workdir` accepts native, MSYS and WSL-automount forms.** A model told that the dialect's paths are MSYS- or WSL-shaped will naturally write `workdir` that way, so the resolver translates a single-letter drive form (`/c/...`) and the WSL automount form (`/mnt/c/...`) into the native `C:\...` before the executor hands the value to `spawn`; MSYS POSIX roots (`/etc`, `/usr`, `/tmp`) and distro-side paths such as `/mnt/data` are left untouched, and a relative `workdir` still resolves against the session workspace. Before that translation an MSYS-form `workdir` failed as `spawn <shell> ENOENT` — a missing-shell symptom for what is really an unusable cwd.
|
|
84
|
+
|
|
85
|
+
Each tool row also registers a short system-prompt section (`tool:<name>`): check the `[exit code: N]` marker on every result, and chain dependent steps with `&&` or `set -o pipefail` — `;` never stops on failure, and `cmd | tail` returns the status of `tail`, not of `cmd`. That is guidance about *composing* a multi-step command rather than about one call's arguments, so it belongs to the prompt instead of the per-call schema; it also makes the runtime's own tail truncation the reason not to bound output with a pipe.
|
|
86
|
+
|
|
81
87
|
## Path conversion (MSYS auto-rewriting)
|
|
82
88
|
|
|
83
89
|
Git Bash rewrites leading-slash POSIX paths into Windows paths (e.g. `<Git root>\root`) whenever a native Windows program is called — standard MSYS behavior, not a plugin defect. Calling `wsl.exe` (or any native exe) with POSIX paths from inside `git_bash` therefore fails:
|
package/README.zh.md
CHANGED
|
@@ -70,7 +70,9 @@ wsl.exe -d Ubuntu-24.04 -e bash -c "command -v bwrap && bwrap --version" # 验
|
|
|
70
70
|
|
|
71
71
|
## 工具提示词(面向模型的描述)
|
|
72
72
|
|
|
73
|
-
`git_bash` / `wsl_bash` 的工具描述刻意保持精简,与官方 `tool-pwsh` 同构:每次调用全新 shell、方言的路径/环境变量写法、`[exit code: N]` 标记、`$DSH_*`
|
|
73
|
+
`git_bash` / `wsl_bash` 的工具描述刻意保持精简,与官方 `tool-pwsh` 同构:每次调用全新 shell、方言的路径/环境变量写法、`[exit code: N]` 标记、`$DSH_*` 环境事实、沙箱行为、输出截断、删除/移动前的目标路径校验、未设变量的 `${VAR:?}` 兜底、后台任务与升级契约。更长的方言说明(MSYS 路径改写、WSL base64 载荷)放在本文档而不是模型可见的描述里。
|
|
74
|
+
|
|
75
|
+
这两个工具都用 `bash -c`,因此上述安全提示采用 `tool-bash` 的 bash 版措辞而非 `tool-pwsh` 的:bash 里 `$HOME` 是可赋值的普通变量,pwsh 的「不要给自动变量赋值」那句在此会误导;bash 对计算路径的兜底就是上面的 `${VAR:?}` 写法——它让未设变量直接报错,而不是静默展开成空串。
|
|
74
76
|
|
|
75
77
|
`git_bash` 的描述还带一条路径格式提示:
|
|
76
78
|
|
|
@@ -78,6 +80,10 @@ wsl.exe -d Ubuntu-24.04 -e bash -c "command -v bwrap && bwrap --version" # 验
|
|
|
78
80
|
|
|
79
81
|
即命令输出里的 MSYS 路径(如 `/d/WorkSpace/foo`)在交给 dsh 文件工具前要转成 Windows 形式(`D:\WorkSpace\foo`);而在 bash 命令内部,MSYS 路径才是 shell 期望的写法。
|
|
80
82
|
|
|
83
|
+
**`workdir` 接受原生、MSYS 与 WSL 挂载三种写法。** 模型在被告知方言路径是 MSYS/WSL 形式后,自然会用同样的形式写 `workdir`;解析器因此把单字母盘符形式(`/c/...`)与 WSL 挂载形式(`/mnt/c/...`)在交给 `spawn` 之前转成原生 `C:\...`,而 MSYS 的 POSIX 根(`/etc`、`/usr`、`/tmp`)与发行版侧路径(如 `/mnt/data`)保持原样,相对路径仍相对会话工作区解析。在此转换之前,MSYS 形式的 `workdir` 会以 `spawn <shell> ENOENT` 失败——一个「找不到 shell」的假象,实际是 cwd 不可用。
|
|
84
|
+
|
|
85
|
+
工具行还会注册一段系统提示词小节(`tool:<name>`):每次结果都要核对 `[exit code: N]` 标记,且依赖前一步的后续命令要用 `&&` 或 `set -o pipefail` 串接——`;` 不会因失败中止,而 `cmd | tail` 返回的是 `tail` 的状态而非 `cmd` 的。这属于「如何组合多步命令」的跨调用指导,因此放在提示词里而不是单次调用的 schema 里;它也让运行时自带的截尾能力成为「不必用管道限制输出」的理由。
|
|
86
|
+
|
|
81
87
|
## 路径转换(MSYS 自动改写)
|
|
82
88
|
|
|
83
89
|
Git Bash 在调用原生 Windows 程序时会把形如 `/root` 的 POSIX 路径自动改写成 Windows 路径(如 `<Git 根目录>\root`),这是 MSYS 的标准行为,不是本插件的缺陷。在 `git_bash` 里直接调用 `wsl.exe`(或其他原生 exe)并传 POSIX 路径时会被改写而失败:
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* @module @deepseek-ai/dsh-tool-bash/factory
|
|
9
9
|
*/
|
|
10
10
|
import z from '@deepseek-ai/schemastery';
|
|
11
|
+
import { existsSync } from 'node:fs';
|
|
11
12
|
import { isAbsolute, resolve as resolvePath } from 'node:path';
|
|
12
13
|
import { defineTool, TOOL_ABORTED } from '@deepseek-ai/dsh-tools';
|
|
13
14
|
import { HarnessError } from '@deepseek-ai/dsh-llm';
|
|
@@ -36,6 +37,15 @@ const DIALECT_FACTS = {
|
|
|
36
37
|
const TOOL_CONFIG = {
|
|
37
38
|
enableRunInBackground: z.boolean().default(true),
|
|
38
39
|
};
|
|
40
|
+
/**
|
|
41
|
+
* Cross-call exit-status guidance for one shell tool instance. It belongs in the
|
|
42
|
+
* prompt rather than the tool schema because the trap is not about one call's
|
|
43
|
+
* arguments but about how a multi-step command is composed: `;` never stops on
|
|
44
|
+
* failure, and a pipeline reports only its last command's status — so the
|
|
45
|
+
* reflex of bounding long output with `| tail`, which this runtime already
|
|
46
|
+
* truncates for the caller, returns `tail`'s status instead of the command's.
|
|
47
|
+
*/
|
|
48
|
+
export const SHELL_EXIT_STATUS_SECTION = 'Check the [exit code: N] marker on every bash result; investigate failures before moving on. Chain dependent steps with `&&` or `set -o pipefail`: `;` never stops on failure, and `cmd | tail` returns the status of `tail`, not of `cmd`.';
|
|
39
49
|
/**
|
|
40
50
|
* The model-facing description of one shell tool instance. The POSIX variant
|
|
41
51
|
* keeps the legacy wording byte-for-byte (the ACP/headless tool-schema
|
|
@@ -74,6 +84,8 @@ export function shellDescription(dialect, backgroundEnabled, escalationModes) {
|
|
|
74
84
|
+ `Current harness environment facts are exposed through managed \`\$${DSH_ENV_PREFIX}*\` variables; inspect them when needed. `
|
|
75
85
|
+ 'Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under <mode> mode]` — a policy denial, not a bug in the command; do not retry another way. '
|
|
76
86
|
+ 'Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. '
|
|
87
|
+
+ '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. '
|
|
88
|
+
+ 'An unset variable expands to an empty string, so guard variables in such paths with `${VAR:?}`. '
|
|
77
89
|
+ background;
|
|
78
90
|
return base + escalationTail(escalationModes);
|
|
79
91
|
}
|
|
@@ -147,11 +159,38 @@ function presentShellResult(args, result) {
|
|
|
147
159
|
const { body, ...exit } = parseExitStatus(raw);
|
|
148
160
|
return { card: 'terminal', output: body, ...exit };
|
|
149
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* Translate the bash-dialect drive forms a model may hand in as `workdir` into
|
|
164
|
+
* the native path the process spawn needs. `node:path` calls `/d/WorkSpace` and
|
|
165
|
+
* the WSL automount view `/mnt/d/WorkSpace` absolute, but Windows resolves them
|
|
166
|
+
* against the current drive (`G:\g\LAB\...`), so the spawn fails and reports
|
|
167
|
+
* ENOENT against the shell executable — `bash.exe` / `wsl.exe` — which reads as
|
|
168
|
+
* a missing shell rather than an unusable directory. Only a single-letter first
|
|
169
|
+
* segment (optionally under `/mnt/`) whose drive exists qualifies, so MSYS POSIX
|
|
170
|
+
* roots (`/etc`, `/usr`, `/tmp`, `/dev`, …) and distro-side paths (`/mnt/data`)
|
|
171
|
+
* are never drive-mapped.
|
|
172
|
+
* @param workdir - an absolute model-supplied workdir.
|
|
173
|
+
* @returns its native Windows form, or the input when it is not a drive form.
|
|
174
|
+
*/
|
|
175
|
+
export function toNativeWorkdir(workdir) {
|
|
176
|
+
if (process.platform !== 'win32')
|
|
177
|
+
return workdir;
|
|
178
|
+
const match = /^(?:\/mnt)?\/([A-Za-z])(?:\/(.*))?$/.exec(workdir);
|
|
179
|
+
if (match === null)
|
|
180
|
+
return workdir;
|
|
181
|
+
const drive = match[1].toUpperCase();
|
|
182
|
+
if (!existsSync(`${drive}:\\`))
|
|
183
|
+
return workdir;
|
|
184
|
+
const rest = match[2];
|
|
185
|
+
return rest === undefined ? `${drive}:\\` : `${drive}:\\${rest.replace(/\//g, '\\')}`;
|
|
186
|
+
}
|
|
150
187
|
/**
|
|
151
188
|
* Resolve an explicit workdir first, making a relative one session-workspace-relative;
|
|
152
189
|
* otherwise use the filesystem identity of the session cwd and leave executor
|
|
153
190
|
* defaulting as the fallback. A resolved sandbox-policy root wins so workdir
|
|
154
|
-
* and confinement use the exact same per-call identity.
|
|
191
|
+
* and confinement use the exact same per-call identity. An absolute dialect-form
|
|
192
|
+
* workdir is translated to its native form (see {@link toNativeWorkdir}) because
|
|
193
|
+
* the executor hands the value straight to `spawn` as the child's `cwd`.
|
|
155
194
|
*/
|
|
156
195
|
function resolveWorkdir(modelWorkdir, exec, policyWorkspaceRoot) {
|
|
157
196
|
const headerCwd = exec.agent?.session.header.cwd;
|
|
@@ -161,7 +200,7 @@ function resolveWorkdir(modelWorkdir, exec, policyWorkspaceRoot) {
|
|
|
161
200
|
if (sessionCwd !== undefined && !isAbsolute(modelWorkdir)) {
|
|
162
201
|
return resolvePath(sessionCwd, modelWorkdir);
|
|
163
202
|
}
|
|
164
|
-
return modelWorkdir;
|
|
203
|
+
return toNativeWorkdir(modelWorkdir);
|
|
165
204
|
}
|
|
166
205
|
/** Detach the executor DTO from readonly Service Definition types into plain JSON data. */
|
|
167
206
|
function canonicalShellResult(result) {
|
|
@@ -274,7 +313,7 @@ export function defineShellTool(def) {
|
|
|
274
313
|
ctx.systemPrompt.section({
|
|
275
314
|
name: `tool:${def.toolName}`,
|
|
276
315
|
order: 105,
|
|
277
|
-
text:
|
|
316
|
+
text: SHELL_EXIT_STATUS_SECTION,
|
|
278
317
|
});
|
|
279
318
|
ctx.tools.register(defineTool({
|
|
280
319
|
name: def.toolName,
|
|
@@ -289,7 +328,7 @@ export function defineShellTool(def) {
|
|
|
289
328
|
+ '"git status" → "Show working tree status"; "npm install" → "Install package dependencies".',
|
|
290
329
|
},
|
|
291
330
|
timeoutMs: { type: 'number', description: 'Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry.' },
|
|
292
|
-
workdir: { type: 'string', description: 'Working directory for this command. Defaults to the session workspace; a relative path is resolved against it.' },
|
|
331
|
+
workdir: { type: 'string', description: 'Working directory for this command. Defaults to the session workspace; a relative path is resolved against it. Native (`C:\\...`), MSYS drive (`/c/...`) and WSL automount (`/mnt/c/...`) forms are all accepted.' },
|
|
293
332
|
...backgroundEnabled ? {
|
|
294
333
|
run_in_background: { type: 'boolean', description: 'Run in the background and return a job id immediately (collect with job_output, stop with job_kill). No timeout applies.' },
|
|
295
334
|
} : {},
|