dsh-win-multi-bash 0.1.0
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/LICENSE +21 -0
- package/README.i18n.yaml +8 -0
- package/README.md +162 -0
- package/README.zh.md +162 -0
- package/THIRD_PARTY_NOTICES +43 -0
- package/cordis.patch.yml +64 -0
- package/lib/bash-git/index.js +382 -0
- package/lib/bash-git/invariant.js +23 -0
- package/lib/bash-wsl/index.js +335 -0
- package/lib/bash-wsl/invariant.js +23 -0
- package/lib/index.js +20 -0
- package/lib/shell-select/index.js +169 -0
- package/lib/shell-select/invariant.js +23 -0
- package/lib/tool-bash/index.js +526 -0
- package/lib/tool-bash/invariant.js +23 -0
- package/lib/tool-bash/types/background.js +25 -0
- package/lib/tool-bash/types/factory.js +390 -0
- package/lib/tool-bash/types/git-bash.js +13 -0
- package/lib/tool-bash/types/index.js +16 -0
- package/lib/tool-bash/types/invariant.js +22 -0
- package/lib/tool-bash/types/render.js +96 -0
- package/lib/tool-bash/types/wsl-bash.js +13 -0
- package/lib/vendor/bwrap-profiles.js +32 -0
- package/lib/vendor/helpers.js +111 -0
- package/package.json +95 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import z from "@deepseek-ai/schemastery";
|
|
3
|
+
import { LocalBashExecutor } from "@deepseek-ai/dsh-bash-local";
|
|
4
|
+
import { classifyDenial, classifyRunnerFailure, isRunnerSpawnFailure, matchesSignature } from "../vendor/helpers.js";
|
|
5
|
+
import { lstatSync } from "node:fs";
|
|
6
|
+
import { basename, dirname, join } from "node:path";
|
|
7
|
+
/**
|
|
8
|
+
* Derive a config schema from a base object schema WITHOUT mutating it.
|
|
9
|
+
* schemastery's Schema#set() mutates the shared `dict` in place, so chaining
|
|
10
|
+
* .set() on LocalBashExecutor.Config lets whichever subclass module loads
|
|
11
|
+
* last overwrite the earlier one's fields — git-bash's probeTimeoutMs default
|
|
12
|
+
* silently became wsl-bash's 30000 and its sandbox union gained the invalid
|
|
13
|
+
* 'bwrap' value. Spreading the base dict into a fresh z.object makes every
|
|
14
|
+
* derivation independent of both the base and other subclasses.
|
|
15
|
+
*/
|
|
16
|
+
function deriveConfig(base, fields) {
|
|
17
|
+
const derived = z.object({ ...base.dict });
|
|
18
|
+
if (base.meta !== void 0) derived.meta = { ...base.meta };
|
|
19
|
+
for (const key of Object.keys(fields)) derived.set(key, fields[key]);
|
|
20
|
+
return derived;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The PATH handed to MSYS bash: the resolved Git installation's layout dirs
|
|
24
|
+
* (`<root>\cmd`, `<root>\bin`, `<root>\usr\bin`) prepended to the inherited
|
|
25
|
+
* PATH. The spawn environment is the scrubbed parent env with explicit
|
|
26
|
+
* overrides, so without this the Git toolchain (sleep, grep, git, ...) would
|
|
27
|
+
* be invisible inside bash even though bash.exe itself is spawned by absolute
|
|
28
|
+
* path. The root is inferred from the bash location (`<root>\usr\bin\bash.exe`
|
|
29
|
+
* or `<root>\bin\bash.exe`); when the location does not look like a Git layout
|
|
30
|
+
* (e.g. a PATH-resolved foreign bash) only its own directory is prepended.
|
|
31
|
+
*/
|
|
32
|
+
function gitToolPath(bashPath, inheritedPath) {
|
|
33
|
+
const binDir = dirname(bashPath);
|
|
34
|
+
const base = basename(binDir).toLowerCase();
|
|
35
|
+
const parent = dirname(binDir);
|
|
36
|
+
const parentBase = basename(parent).toLowerCase();
|
|
37
|
+
let root;
|
|
38
|
+
if (base === "cmd") root = parent;
|
|
39
|
+
else if (base === "bin") root = parentBase === "usr" ? dirname(parent) : parent;
|
|
40
|
+
const dirs = [];
|
|
41
|
+
if (root !== void 0) {
|
|
42
|
+
for (const dir of [join(root, "cmd"), join(root, "bin"), join(root, "usr", "bin")]) {
|
|
43
|
+
if (!dirs.includes(dir)) dirs.push(dir);
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
dirs.push(binDir);
|
|
47
|
+
}
|
|
48
|
+
const rest = (inheritedPath ?? "").split(";").filter((entry) => entry.length > 0);
|
|
49
|
+
return [...dirs, ...rest].join(";");
|
|
50
|
+
}
|
|
51
|
+
//#region lib/types/resolve.js
|
|
52
|
+
/**
|
|
53
|
+
* Git Bash executable resolution for the bash-git executor, dependency-free
|
|
54
|
+
* and parameterized (env/platform) so resolution is a pure function of its
|
|
55
|
+
* inputs on every platform — the twin of dsh-pwsh-local's resolvePwshPath.
|
|
56
|
+
* @module @deepseek-ai/dsh-bash-git/resolve
|
|
57
|
+
*/
|
|
58
|
+
/**
|
|
59
|
+
* Infer Git for Windows install roots from PATH entries shaped like its
|
|
60
|
+
* layout directories (`<root>\cmd`, `<root>\bin`, `<root>\usr\bin`) that
|
|
61
|
+
* actually contain `git.exe`. This lets resolution find a Git Bash that lives
|
|
62
|
+
* outside the well-known Program Files probes (e.g. `D:\Program Files\Git`
|
|
63
|
+
* with only `cmd` on PATH) without any manual pin. Only directories carrying a
|
|
64
|
+
* real `git.exe` qualify, so unrelated `bin` dirs (Strawberry Perl, Scoop
|
|
65
|
+
* shims, ...) are never mistaken for Git roots.
|
|
66
|
+
* @param env - the environment to probe; defaults to the process environment.
|
|
67
|
+
* @returns Git install roots in PATH order.
|
|
68
|
+
*/
|
|
69
|
+
function gitRootCandidates(env = process.env) {
|
|
70
|
+
const roots = [];
|
|
71
|
+
const seen = new Set();
|
|
72
|
+
for (const entry of (env.PATH ?? "").split(";")) {
|
|
73
|
+
const trimmed = entry.trim().replace(/^"|"$/g, "");
|
|
74
|
+
if (trimmed.length === 0) continue;
|
|
75
|
+
const base = basename(trimmed).toLowerCase();
|
|
76
|
+
if (base !== "cmd" && base !== "bin") continue;
|
|
77
|
+
const parent = dirname(trimmed);
|
|
78
|
+
const root = base === "cmd" || basename(parent).toLowerCase() !== "usr" ? parent : dirname(parent);
|
|
79
|
+
if (!candidateExists(join(trimmed, "git.exe"))) continue;
|
|
80
|
+
const key = root.toLowerCase();
|
|
81
|
+
if (!seen.has(key)) {
|
|
82
|
+
seen.add(key);
|
|
83
|
+
roots.push(root);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return roots;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Well-known Git for Windows install locations plus PATH entries. The Windows
|
|
90
|
+
* WSL launcher (`System32\bash.exe`) is deliberately excluded: this executor is
|
|
91
|
+
* MSYS Git Bash, and silently routing it into WSL would make a non-WSL tool
|
|
92
|
+
* depend on WSL — resolution then either finds a real Git Bash or fails loud
|
|
93
|
+
* with the `bashPath` hint. PATH entries shaped like Git layout directories
|
|
94
|
+
* with a real `git.exe` additionally contribute their `<root>\usr\bin` and
|
|
95
|
+
* `<root>\bin` bash candidates, so an install reachable through `git` on PATH
|
|
96
|
+
* is found automatically.
|
|
97
|
+
* @param env - the environment to probe; defaults to the process environment.
|
|
98
|
+
* @returns candidate `bash.exe` paths in resolution order.
|
|
99
|
+
*/
|
|
100
|
+
function candidateBashPaths(env = process.env) {
|
|
101
|
+
const programFiles = env.ProgramFiles ?? "C:\\Program Files";
|
|
102
|
+
const programFilesX86 = env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)";
|
|
103
|
+
const wslLauncher = join(env.SystemRoot ?? "C:\\Windows", "System32", "bash.exe").toLowerCase();
|
|
104
|
+
const candidates = [
|
|
105
|
+
join(programFiles, "Git", "bin", "bash.exe"),
|
|
106
|
+
join(programFiles, "Git", "usr", "bin", "bash.exe"),
|
|
107
|
+
join(programFilesX86, "Git", "bin", "bash.exe")
|
|
108
|
+
];
|
|
109
|
+
for (const entry of (env.PATH ?? "").split(";")) {
|
|
110
|
+
const trimmed = entry.trim().replace(/^"|"$/g, "");
|
|
111
|
+
if (trimmed.length === 0) continue;
|
|
112
|
+
const candidate = join(trimmed, "bash.exe");
|
|
113
|
+
if (candidate.toLowerCase() === wslLauncher) continue;
|
|
114
|
+
candidates.push(candidate);
|
|
115
|
+
}
|
|
116
|
+
for (const root of gitRootCandidates(env)) {
|
|
117
|
+
for (const candidate of [join(root, "usr", "bin", "bash.exe"), join(root, "bin", "bash.exe")]) {
|
|
118
|
+
if (!candidates.includes(candidate)) candidates.push(candidate);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return candidates;
|
|
122
|
+
}
|
|
123
|
+
function candidateExists(candidate) {
|
|
124
|
+
try {
|
|
125
|
+
const stat = lstatSync(candidate);
|
|
126
|
+
return stat.isFile() || stat.isSymbolicLink();
|
|
127
|
+
} catch {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Resolve the Git Bash executable this executor spawns. Returns undefined on
|
|
133
|
+
* win32 when no candidate exists (the executor turns that into a loud
|
|
134
|
+
* route-time error naming the probes); non-win32 hosts resolve a bare
|
|
135
|
+
* `bash` for PATH resolution, mirroring resolvePwshPath.
|
|
136
|
+
* @param configured - an explicit `bashPath` config value, trusted as-is.
|
|
137
|
+
* @param env - the environment to probe on Windows; defaults to the process environment.
|
|
138
|
+
* @param platform - the platform to resolve for; defaults to the process platform.
|
|
139
|
+
* @returns the first existing well-known location on win32, else `bash`, else undefined.
|
|
140
|
+
*/
|
|
141
|
+
function resolveBashPath(configured, env = process.env, platform = process.platform) {
|
|
142
|
+
if (configured !== void 0 && configured.length > 0) return configured;
|
|
143
|
+
if (platform === "win32") {
|
|
144
|
+
for (const candidate of candidateBashPaths(env)) if (candidateExists(candidate)) return candidate;
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
return "bash";
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Registry-backed Git Bash locations: Git for Windows always records its
|
|
151
|
+
* install path at `HKLM\SOFTWARE\GitForWindows\InstallPath`, which is the
|
|
152
|
+
* reliable signal for installs outside every probe path (custom drives,
|
|
153
|
+
* portable installs, ...). Read via `reg query` (spawnSync) — any failure is
|
|
154
|
+
* an empty list, so a missing registry never breaks resolution.
|
|
155
|
+
* @returns the first existing bash.exe under the registered install, or undefined.
|
|
156
|
+
*/
|
|
157
|
+
function registryBashPaths() {
|
|
158
|
+
if (process.platform !== "win32") return;
|
|
159
|
+
try {
|
|
160
|
+
const run = spawnSync("reg", ["query", "HKLM\\SOFTWARE\\GitForWindows", "/v", "InstallPath"], {
|
|
161
|
+
encoding: "utf8",
|
|
162
|
+
timeout: 1e4
|
|
163
|
+
});
|
|
164
|
+
if (run.status !== 0 || run.error !== void 0) return;
|
|
165
|
+
const line = run.stdout.split(/\r?\n/).find((l) => /InstallPath/i.test(l) && /REG_SZ/i.test(l));
|
|
166
|
+
const install = line?.split("REG_SZ")[1]?.trim();
|
|
167
|
+
if (install === void 0 || install.length === 0) return;
|
|
168
|
+
for (const candidate of [join(install, "usr", "bin", "bash.exe"), join(install, "bin", "bash.exe")]) {
|
|
169
|
+
if (candidateExists(candidate)) return candidate;
|
|
170
|
+
}
|
|
171
|
+
} catch {
|
|
172
|
+
/* registry probe must never break resolution */
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
//#endregion
|
|
176
|
+
//#region lib/types/index.js
|
|
177
|
+
/**
|
|
178
|
+
* Git Bash Service Provider for the bash capability seam: a LocalBashExecutor
|
|
179
|
+
* whose argv is `[bashPath, '-c', command]` (MSYS bash, clean profile-less
|
|
180
|
+
* environment). Sandbox policy `auto` probes whether the windows-acl runner
|
|
181
|
+
* can launch Cygwin bash (once, lazily, at the first sandboxMode read or the
|
|
182
|
+
* first routed command); when the probe fails, commands run unconfined and
|
|
183
|
+
* results honestly carry no sandbox facts (the declared auto contract).
|
|
184
|
+
* `none` never consults `ctx.sandbox`. The executable is resolved lazily at
|
|
185
|
+
* the first routed command and fails loud, naming the probes, when Git Bash
|
|
186
|
+
* is missing.
|
|
187
|
+
* @module @deepseek-ai/dsh-bash-git
|
|
188
|
+
*/
|
|
189
|
+
/**
|
|
190
|
+
* Git Bash executor over the local bash mechanics: argv is the resolved
|
|
191
|
+
* `bash.exe` followed by `-c` and the command, and the
|
|
192
|
+
* sandbox stance (`auto`/`none`) is applied lazily per the
|
|
193
|
+
* module doc's probe contract.
|
|
194
|
+
*/
|
|
195
|
+
var GitBashExecutor = class extends LocalBashExecutor {
|
|
196
|
+
static Config = deriveConfig(LocalBashExecutor.Config, {
|
|
197
|
+
bashPath: z.string(),
|
|
198
|
+
sandbox: z.union([z.const("auto"), z.const("none")]).default("auto"),
|
|
199
|
+
probeTimeoutMs: z.natural().default(1e4),
|
|
200
|
+
requireSandbox: z.boolean().default(false)
|
|
201
|
+
});
|
|
202
|
+
/** Test hook mirroring the sandbox-local internals pattern. */
|
|
203
|
+
internals = {};
|
|
204
|
+
sandboxStance;
|
|
205
|
+
probeTimeoutMs;
|
|
206
|
+
confinedVerdict;
|
|
207
|
+
constructor(ctx, config) {
|
|
208
|
+
super(ctx, config);
|
|
209
|
+
const entry = config;
|
|
210
|
+
this.sandboxStance = entry.sandbox;
|
|
211
|
+
this.probeTimeoutMs = entry.probeTimeoutMs;
|
|
212
|
+
this.requireSandbox = entry.requireSandbox;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* The capability fact: the policy default mode while confinement is usable.
|
|
216
|
+
* With `requireSandbox`, the mode is declared even when the probe fails, so
|
|
217
|
+
* the tool layer advertises escalation (`sandbox_permissions`) and the
|
|
218
|
+
* executor can refuse unconfined runs outside danger-full-access.
|
|
219
|
+
*/
|
|
220
|
+
get sandboxMode() {
|
|
221
|
+
if (this.sandboxStance === "none") return void 0;
|
|
222
|
+
if (this.requireSandbox) return this.ctx.sandboxPolicy.defaultMode;
|
|
223
|
+
return this.probeConfinedOnce() ? this.ctx.sandboxPolicy.defaultMode : void 0;
|
|
224
|
+
}
|
|
225
|
+
/** The exact argv every (un)confined command runs through. */
|
|
226
|
+
gitArgv(spec) {
|
|
227
|
+
return [
|
|
228
|
+
this.bashPath(),
|
|
229
|
+
"-c",
|
|
230
|
+
spec.command
|
|
231
|
+
];
|
|
232
|
+
}
|
|
233
|
+
/** The resolved Git Bash executable; undefined resolves to a loud route-time error. */
|
|
234
|
+
tryBashPath() {
|
|
235
|
+
const path = (this.internals.resolveBashPath ?? ((configured) => resolveBashPath(configured)))(this.config.bashPath);
|
|
236
|
+
if (path !== void 0) return path;
|
|
237
|
+
return (this.internals.registryBashPaths ?? registryBashPaths)();
|
|
238
|
+
}
|
|
239
|
+
/** Route-time loud failure naming the probes, per the spec error contract. */
|
|
240
|
+
bashPath() {
|
|
241
|
+
const path = this.tryBashPath();
|
|
242
|
+
if (path !== void 0) return path;
|
|
243
|
+
throw new Error(`bash-git: Git Bash was not found (probed ${candidateBashPaths().join(", ")} and the GitForWindows registry). Install Git for Windows or set gitBash.bashPath in the shell-select config.`);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Probe whether the windows-acl runner can launch bash.exe, once per
|
|
247
|
+
* executor lifetime (lazy: the first sandboxMode read or the first routed
|
|
248
|
+
* command). Runs a trivial confined `exit 0` synchronously (mirroring
|
|
249
|
+
* sandbox-local's defaultProbeWindowsAcl shape); a spawn rejection, a
|
|
250
|
+
* runner-failure exit, or a missing bash.exe all mean no confinement.
|
|
251
|
+
*/
|
|
252
|
+
probeConfinedOnce() {
|
|
253
|
+
if (this.confinedVerdict !== void 0) return this.confinedVerdict;
|
|
254
|
+
const probe = this.internals.probeConfined;
|
|
255
|
+
if (probe !== void 0) return this.confinedVerdict = probe();
|
|
256
|
+
const bashPath = this.tryBashPath();
|
|
257
|
+
if (bashPath === void 0) return this.confinedVerdict = false;
|
|
258
|
+
const root = this.ctx.sandboxPolicy.resolve().workspaceRoot;
|
|
259
|
+
let confined;
|
|
260
|
+
try {
|
|
261
|
+
confined = this.ctx.sandbox.confine([
|
|
262
|
+
bashPath,
|
|
263
|
+
"-c",
|
|
264
|
+
"exit 0"
|
|
265
|
+
], {
|
|
266
|
+
mode: "read-only",
|
|
267
|
+
workspaceRoot: root
|
|
268
|
+
});
|
|
269
|
+
} catch {
|
|
270
|
+
return this.confinedVerdict = false;
|
|
271
|
+
}
|
|
272
|
+
const runner = confined.argv[0];
|
|
273
|
+
if (runner === void 0) return this.confinedVerdict = false;
|
|
274
|
+
const probeRun = spawnSync(runner, confined.argv.slice(1), {
|
|
275
|
+
timeout: this.probeTimeoutMs,
|
|
276
|
+
cwd: root,
|
|
277
|
+
stdio: "ignore"
|
|
278
|
+
});
|
|
279
|
+
return this.confinedVerdict = probeRun.status === 0;
|
|
280
|
+
}
|
|
281
|
+
confinedPolicy(spec) {
|
|
282
|
+
return spec.sandboxPolicy ?? this.ctx.sandboxPolicy.resolve();
|
|
283
|
+
}
|
|
284
|
+
async run(spec) {
|
|
285
|
+
const policy = this.confinedPolicy(spec);
|
|
286
|
+
if (policy.mode === "danger-full-access") return {
|
|
287
|
+
...await this.runArgv(spec, this.gitArgv(spec)),
|
|
288
|
+
sandbox: {
|
|
289
|
+
mode: "danger-full-access",
|
|
290
|
+
denied: false
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
if (this.sandboxStance === "none") return this.runArgv(spec, this.gitArgv(spec));
|
|
294
|
+
if (!this.probeConfinedOnce()) {
|
|
295
|
+
if (this.requireSandbox) throw new Error("bash-git: sandbox unavailable — the windows-acl runner cannot confine MSYS bash (probe failed); refusing to run unconfined under " + policy.mode + " mode. Fix the sandbox or retry the command with sandbox_permissions: \"danger-full-access\" and a justification.");
|
|
296
|
+
return this.runArgv(spec, this.gitArgv(spec));
|
|
297
|
+
}
|
|
298
|
+
const confined = this.ctx.sandbox.confine(this.gitArgv(spec), {
|
|
299
|
+
...policy,
|
|
300
|
+
mode: policy.mode
|
|
301
|
+
});
|
|
302
|
+
let result;
|
|
303
|
+
try {
|
|
304
|
+
result = await this.runArgv(spec, confined.argv);
|
|
305
|
+
} catch (error) {
|
|
306
|
+
if (spec.signal?.aborted === true) spec.signal.throwIfAborted();
|
|
307
|
+
if (isRunnerSpawnFailure(error, confined.argv[0], spec.workdir)) return this.runArgv(spec, this.gitArgv(spec));
|
|
308
|
+
throw error;
|
|
309
|
+
}
|
|
310
|
+
if (classifyRunnerFailure(result.exitCode, result.stderr.text, confined.runnerFailureRules) !== void 0) return this.runArgv(spec, this.gitArgv(spec));
|
|
311
|
+
return {
|
|
312
|
+
...result,
|
|
313
|
+
sandbox: {
|
|
314
|
+
mode: policy.mode,
|
|
315
|
+
denied: classifyDenial(result, confined.denialSignatures),
|
|
316
|
+
enforcement: confined.enforcement
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
/** Per-process confinement facts retained until settlement (bash-sandbox pattern). */
|
|
321
|
+
processFacts = /* @__PURE__ */ new Map();
|
|
322
|
+
start(spec) {
|
|
323
|
+
const policy = this.confinedPolicy(spec);
|
|
324
|
+
if (policy.mode === "danger-full-access") return this.startArgv(spec, this.gitArgv(spec));
|
|
325
|
+
if (this.sandboxStance === "none") return this.startArgv(spec, this.gitArgv(spec));
|
|
326
|
+
if (!this.probeConfinedOnce()) {
|
|
327
|
+
if (this.requireSandbox) throw new Error("bash-git: sandbox unavailable — the windows-acl runner cannot confine MSYS bash (probe failed); refusing to run unconfined under " + policy.mode + " mode. Fix the sandbox or retry the command with sandbox_permissions: \"danger-full-access\" and a justification.");
|
|
328
|
+
return this.startArgv(spec, this.gitArgv(spec));
|
|
329
|
+
}
|
|
330
|
+
const confined = this.ctx.sandbox.confine(this.gitArgv(spec), {
|
|
331
|
+
...policy,
|
|
332
|
+
mode: policy.mode
|
|
333
|
+
});
|
|
334
|
+
let proc;
|
|
335
|
+
try {
|
|
336
|
+
proc = this.startArgv(spec, confined.argv);
|
|
337
|
+
} catch (error) {
|
|
338
|
+
if (isRunnerSpawnFailure(error, confined.argv[0], spec.workdir)) return this.startArgv(spec, this.gitArgv(spec));
|
|
339
|
+
throw error;
|
|
340
|
+
}
|
|
341
|
+
this.processFacts.set(proc, {
|
|
342
|
+
mode: policy.mode,
|
|
343
|
+
enforcement: confined.enforcement,
|
|
344
|
+
denialSignatures: confined.denialSignatures,
|
|
345
|
+
runnerFailureRules: confined.runnerFailureRules,
|
|
346
|
+
runnerProgram: confined.argv[0],
|
|
347
|
+
workdir: spec.workdir
|
|
348
|
+
});
|
|
349
|
+
return proc;
|
|
350
|
+
}
|
|
351
|
+
/** Stamp per-process sandbox facts before `done` settles (bash-sandbox semantics). */
|
|
352
|
+
onProcessDone(proc, stderr, spawnFailed, spawnError) {
|
|
353
|
+
const facts = this.processFacts.get(proc);
|
|
354
|
+
if (facts !== void 0) {
|
|
355
|
+
this.processFacts.delete(proc);
|
|
356
|
+
proc.sandbox = (spawnFailed ? isRunnerSpawnFailure(spawnError, facts.runnerProgram, facts.workdir) : classifyRunnerFailure(proc.exitCode, stderr, facts.runnerFailureRules) !== void 0) ? {
|
|
357
|
+
mode: facts.mode,
|
|
358
|
+
denied: false,
|
|
359
|
+
enforcement: facts.enforcement,
|
|
360
|
+
runnerFailed: true
|
|
361
|
+
} : {
|
|
362
|
+
mode: facts.mode,
|
|
363
|
+
denied: matchesSignature(proc.exitCode, stderr, facts.denialSignatures),
|
|
364
|
+
enforcement: facts.enforcement
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
super.onProcessDone(proc, stderr, spawnFailed, spawnError);
|
|
368
|
+
}
|
|
369
|
+
resolve(request) {
|
|
370
|
+
const bashPath = this.tryBashPath();
|
|
371
|
+
const env = bashPath === void 0 ? request.env : {
|
|
372
|
+
...request.env,
|
|
373
|
+
PATH: gitToolPath(bashPath, request.env?.PATH ?? process.env.PATH ?? "")
|
|
374
|
+
};
|
|
375
|
+
return {
|
|
376
|
+
...super.resolve({ ...request, env }),
|
|
377
|
+
sandboxPolicy: request.sandboxPolicy ?? this.ctx.sandboxPolicy.resolve()
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
//#endregion
|
|
382
|
+
export { GitBashExecutor, GitBashExecutor as default, candidateBashPaths, gitRootCandidates, gitToolPath, registryBashPaths, resolveBashPath };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region lib/types/invariant.js
|
|
2
|
+
/**
|
|
3
|
+
* Package-owned invariant companion for `@deepseek-ai/dsh-bash-git`.
|
|
4
|
+
* @module @deepseek-ai/dsh-bash-git/invariant
|
|
5
|
+
*/
|
|
6
|
+
const PACKAGE_NAME = "@deepseek-ai/dsh-bash-git";
|
|
7
|
+
/** Cordis companion plugin name. */
|
|
8
|
+
const name = "bash-git-invariant";
|
|
9
|
+
/** Service required before the companion can reserve package ownership. */
|
|
10
|
+
const inject = ["invariants"];
|
|
11
|
+
/**
|
|
12
|
+
* No runtime invariant: this package exposes no independent event sequence or mutable data relation
|
|
13
|
+
* beyond contracts enforced at its owning seam.
|
|
14
|
+
*/
|
|
15
|
+
const install = () => {};
|
|
16
|
+
/**
|
|
17
|
+
* Register this package's invariant companion.
|
|
18
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
19
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
20
|
+
*/
|
|
21
|
+
const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
22
|
+
//#endregion
|
|
23
|
+
export { apply, inject, name };
|