dsh-win-multi-bash 0.2.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 +40 -22
- package/README.zh.md +39 -21
- package/THIRD_PARTY_NOTICES +2 -3
- package/cordis.patch.yml +48 -54
- package/lib/bash-git/index.js +115 -76
- package/lib/bash-wsl/index.js +71 -37
- package/lib/index.js +9 -6
- package/lib/tool-bash/types/backend.js +41 -0
- package/lib/tool-bash/types/background.js +66 -0
- package/lib/tool-bash/types/factory.js +98 -23
- package/lib/tool-bash/types/git-bash.js +14 -6
- package/lib/tool-bash/types/render.js +0 -29
- package/lib/tool-bash/types/wsl-bash.js +14 -6
- package/package.json +7 -20
- package/lib/bash-git/invariant.js +0 -23
- package/lib/bash-wsl/invariant.js +0 -23
- package/lib/shell-select/index.js +0 -170
- package/lib/shell-select/invariant.js +0 -23
- package/lib/tool-bash/index.js +0 -527
- package/lib/tool-bash/invariant.js +0 -23
- package/lib/tool-bash/types/index.js +0 -16
- package/lib/tool-bash/types/invariant.js +0 -22
|
@@ -8,13 +8,15 @@
|
|
|
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';
|
|
14
15
|
import { ESCALATION_TARGETS, approveEscalation, canonicalPath, validateEscalationArgs } from '@deepseek-ai/dsh-sandbox';
|
|
15
16
|
import { DSH_ENV_PREFIX } from '@deepseek-ai/dsh-shell';
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
17
|
+
import { ownExecutor } from "./backend.js";
|
|
18
|
+
import { processJob, processOutcome, processSources } from "./background.js";
|
|
19
|
+
import { parseExitStatus, renderResult } from "./render.js";
|
|
18
20
|
const DIALECT_FACTS = {
|
|
19
21
|
posix: { shell: 'bash', invoke: 'bash -c', paths: 'POSIX paths', env: '$VAR', toolchain: 'the full Unix toolchain' },
|
|
20
22
|
msys: {
|
|
@@ -31,10 +33,19 @@ const DIALECT_FACTS = {
|
|
|
31
33
|
env: '$VAR',
|
|
32
34
|
},
|
|
33
35
|
};
|
|
34
|
-
/**
|
|
35
|
-
|
|
36
|
+
/** The tool-layer keys every shell tool instance carries, before its backend partition. */
|
|
37
|
+
const TOOL_CONFIG = {
|
|
36
38
|
enableRunInBackground: z.boolean().default(true),
|
|
37
|
-
}
|
|
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`.';
|
|
38
49
|
/**
|
|
39
50
|
* The model-facing description of one shell tool instance. The POSIX variant
|
|
40
51
|
* keeps the legacy wording byte-for-byte (the ACP/headless tool-schema
|
|
@@ -73,6 +84,8 @@ export function shellDescription(dialect, backgroundEnabled, escalationModes) {
|
|
|
73
84
|
+ `Current harness environment facts are exposed through managed \`\$${DSH_ENV_PREFIX}*\` variables; inspect them when needed. `
|
|
74
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. '
|
|
75
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:?}`. '
|
|
76
89
|
+ background;
|
|
77
90
|
return base + escalationTail(escalationModes);
|
|
78
91
|
}
|
|
@@ -146,11 +159,38 @@ function presentShellResult(args, result) {
|
|
|
146
159
|
const { body, ...exit } = parseExitStatus(raw);
|
|
147
160
|
return { card: 'terminal', output: body, ...exit };
|
|
148
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
|
+
}
|
|
149
187
|
/**
|
|
150
188
|
* Resolve an explicit workdir first, making a relative one session-workspace-relative;
|
|
151
189
|
* otherwise use the filesystem identity of the session cwd and leave executor
|
|
152
190
|
* defaulting as the fallback. A resolved sandbox-policy root wins so workdir
|
|
153
|
-
* 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`.
|
|
154
194
|
*/
|
|
155
195
|
function resolveWorkdir(modelWorkdir, exec, policyWorkspaceRoot) {
|
|
156
196
|
const headerCwd = exec.agent?.session.header.cwd;
|
|
@@ -160,7 +200,7 @@ function resolveWorkdir(modelWorkdir, exec, policyWorkspaceRoot) {
|
|
|
160
200
|
if (sessionCwd !== undefined && !isAbsolute(modelWorkdir)) {
|
|
161
201
|
return resolvePath(sessionCwd, modelWorkdir);
|
|
162
202
|
}
|
|
163
|
-
return modelWorkdir;
|
|
203
|
+
return toNativeWorkdir(modelWorkdir);
|
|
164
204
|
}
|
|
165
205
|
/** Detach the executor DTO from readonly Service Definition types into plain JSON data. */
|
|
166
206
|
function canonicalShellResult(result) {
|
|
@@ -197,18 +237,47 @@ const BACKGROUND_OUTPUT_PROPERTIES = {
|
|
|
197
237
|
* tool name, the approval subject, the prompt section name (`tool:<toolName>`),
|
|
198
238
|
* and the `ctx.jobs` kind; the instance module declares its kind in
|
|
199
239
|
* {@link JobKindMap} via declaration merging.
|
|
240
|
+
*
|
|
241
|
+
* The tool owns its executor outright (see `./backend.js`): dsh 0.1.7 dropped
|
|
242
|
+
* the shell seam's routing field, so a tool can no longer ask a shared seat
|
|
243
|
+
* for a-backend-by-name. `def.Executor` is constructed once per tool load and
|
|
244
|
+
* driven directly, leaving `ctx.shell` to the base bundle's own executor.
|
|
200
245
|
* @param def - the instance definition: `toolName` (also the job kind),
|
|
201
|
-
* `
|
|
246
|
+
* `dialect`, `Executor` (the backend class to own), and `configKey` (the
|
|
247
|
+
* Config partition that backend's settings live under).
|
|
202
248
|
* @returns the complete plugin object (name/inject/Config/apply).
|
|
203
249
|
*/
|
|
204
250
|
export function defineShellTool(def) {
|
|
251
|
+
const Config = z.object({
|
|
252
|
+
...TOOL_CONFIG,
|
|
253
|
+
[def.configKey]: def.Executor.Config.default({}),
|
|
254
|
+
});
|
|
205
255
|
return {
|
|
206
256
|
name: `tool-${def.toolName}`,
|
|
207
|
-
|
|
257
|
+
// The last three are the owned executor's own requirements; injecting
|
|
258
|
+
// them here is what lets the backend be built during apply.
|
|
259
|
+
inject: ['tools', 'systemPrompt', 'shellEnv', 'subprocess', 'sandbox', 'sandboxPolicy'],
|
|
208
260
|
Config,
|
|
209
|
-
apply(ctx, config = {}) {
|
|
261
|
+
async apply(ctx, config = {}) {
|
|
210
262
|
const backgroundEnabled = config.enableRunInBackground ?? true;
|
|
211
|
-
const
|
|
263
|
+
const { executor } = ownExecutor(ctx, def.Executor, config[def.configKey]);
|
|
264
|
+
// The confinement probe is async on 0.1.7 (the provider's `confine`
|
|
265
|
+
// returns a promise), so the escalation surface settles before the
|
|
266
|
+
// tool registers: a tool never advertises a confinement it cannot
|
|
267
|
+
// reach.
|
|
268
|
+
//
|
|
269
|
+
// A backend whose probe fails *loud* is deliberately swallowed here
|
|
270
|
+
// — that is the documented lazy contract: an explicit
|
|
271
|
+
// `sandbox: bwrap` without bubblewrap must not brick the tool row
|
|
272
|
+
// at load. It advertises no confinement now and its error
|
|
273
|
+
// resurfaces at the first routed command.
|
|
274
|
+
let defaultMode;
|
|
275
|
+
try {
|
|
276
|
+
defaultMode = await executor.resolveSandboxMode();
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
defaultMode = undefined;
|
|
280
|
+
}
|
|
212
281
|
const escalationModes = defaultMode === undefined ? [] : ESCALATION_TARGETS;
|
|
213
282
|
const sandboxPolicy = defaultMode === undefined ? undefined : ctx.get('sandboxPolicy');
|
|
214
283
|
if (defaultMode !== undefined && sandboxPolicy === undefined) {
|
|
@@ -244,7 +313,7 @@ export function defineShellTool(def) {
|
|
|
244
313
|
ctx.systemPrompt.section({
|
|
245
314
|
name: `tool:${def.toolName}`,
|
|
246
315
|
order: 105,
|
|
247
|
-
text:
|
|
316
|
+
text: SHELL_EXIT_STATUS_SECTION,
|
|
248
317
|
});
|
|
249
318
|
ctx.tools.register(defineTool({
|
|
250
319
|
name: def.toolName,
|
|
@@ -259,7 +328,7 @@ export function defineShellTool(def) {
|
|
|
259
328
|
+ '"git status" → "Show working tree status"; "npm install" → "Install package dependencies".',
|
|
260
329
|
},
|
|
261
330
|
timeoutMs: { type: 'number', description: 'Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry.' },
|
|
262
|
-
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.' },
|
|
263
332
|
...backgroundEnabled ? {
|
|
264
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.' },
|
|
265
334
|
} : {},
|
|
@@ -350,7 +419,6 @@ export function defineShellTool(def) {
|
|
|
350
419
|
command: args.command,
|
|
351
420
|
...workdir !== undefined ? { workdir } : {},
|
|
352
421
|
...args.timeoutMs !== undefined ? { timeoutMs: args.timeoutMs } : {},
|
|
353
|
-
...def.shell !== undefined ? { shell: def.shell } : {},
|
|
354
422
|
dshEnv,
|
|
355
423
|
...policy !== undefined ? { sandboxPolicy: policy } : {},
|
|
356
424
|
};
|
|
@@ -369,26 +437,33 @@ export function defineShellTool(def) {
|
|
|
369
437
|
error.name = 'AbortError';
|
|
370
438
|
throw error;
|
|
371
439
|
}
|
|
440
|
+
// No deadline for a background command, and the process is
|
|
441
|
+
// spawned inside the starter, after the registry admitted it,
|
|
442
|
+
// so the pull sources bind lazily through the `proc` closure.
|
|
443
|
+
const spec = executor.resolve({ ...request, onExpiry: 'none' });
|
|
444
|
+
let proc;
|
|
372
445
|
// Task preflight finishes before the starter can spawn a process.
|
|
373
446
|
const id = jobs.start({
|
|
374
447
|
kind: def.toolName,
|
|
375
448
|
label: args.command,
|
|
376
|
-
...exec.agent ? { owner: exec.agent } : {},
|
|
449
|
+
...exec.agent ? { owner: exec.agent.id } : {},
|
|
450
|
+
output: processSources(() => proc),
|
|
377
451
|
run: () => {
|
|
378
|
-
const
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
};
|
|
452
|
+
const hooks = processJob(async (signal) => {
|
|
453
|
+
proc = await executor.execute({ ...spec, signal });
|
|
454
|
+
return proc;
|
|
455
|
+
}, (started) => processOutcome(started));
|
|
456
|
+
return { done: hooks.done, cancel: (reason) => hooks.cancel(reason) };
|
|
384
457
|
},
|
|
385
458
|
});
|
|
386
459
|
return { kind: 'background', jobId: id };
|
|
387
460
|
}
|
|
388
|
-
|
|
461
|
+
// Foreground is a property of awaiting the handle's result, not
|
|
462
|
+
// of the call: the same `execute` also serves `run_in_background`.
|
|
463
|
+
const result = await (await executor.execute(executor.resolve({
|
|
389
464
|
...request,
|
|
390
465
|
signal: exec.signal,
|
|
391
|
-
}));
|
|
466
|
+
}))).result();
|
|
392
467
|
if (result.aborted) {
|
|
393
468
|
const error = new HarnessError('tool call aborted', TOOL_ABORTED);
|
|
394
469
|
error.name = 'AbortError';
|
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The Git Bash (MSYS) tool instance:
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* The Git Bash (MSYS) tool instance: owns a {@link GitBashExecutor} outright
|
|
3
|
+
* and drives it directly. dsh 0.1.7 removed the shell seam's routing field
|
|
4
|
+
* (`ShellExecRequest.shell`), so there is no shared selector to route through
|
|
5
|
+
* any more — the tool itself is what knows it wants Git Bash.
|
|
6
|
+
* @module dsh-win-multi-bash/tool-bash/git-bash
|
|
5
7
|
*/
|
|
8
|
+
import { GitBashExecutor } from "../../bash-git/index.js";
|
|
6
9
|
import { defineShellTool } from "./factory.js";
|
|
7
|
-
/** The Git Bash tool:
|
|
8
|
-
const tool = defineShellTool({
|
|
10
|
+
/** The Git Bash tool: owns the git-bash backend. */
|
|
11
|
+
const tool = defineShellTool({
|
|
12
|
+
toolName: 'git_bash',
|
|
13
|
+
Executor: GitBashExecutor,
|
|
14
|
+
configKey: 'gitBash',
|
|
15
|
+
dialect: 'msys',
|
|
16
|
+
});
|
|
9
17
|
export const name = tool.name;
|
|
10
18
|
export const inject = tool.inject;
|
|
11
19
|
export const Config = tool.Config;
|
|
12
20
|
export const apply = tool.apply;
|
|
13
|
-
//# sourceMappingURL=git-bash.js.map
|
|
21
|
+
//# sourceMappingURL=git-bash.js.map
|
|
@@ -57,35 +57,6 @@ export function renderResult(result, escalationModes = []) {
|
|
|
57
57
|
body += '\n';
|
|
58
58
|
return body + markers.join('\n');
|
|
59
59
|
}
|
|
60
|
-
/**
|
|
61
|
-
* Shape one background-process read into the `job_output` delta the model
|
|
62
|
-
* sees: the incremental delta, plus the lossy-read notice (with full-stream
|
|
63
|
-
* spill paths) when in-memory truncation dropped unread bytes. Empty-delta
|
|
64
|
-
* rendering (`(no new output)`) is the generic job controller's job.
|
|
65
|
-
* @param read - one incremental read from the process handle.
|
|
66
|
-
* @param sandbox - settled sandbox facts, when this was a confined process.
|
|
67
|
-
* @param escalationModes - escalation targets advertised by this composition.
|
|
68
|
-
* @returns the delta text with any loss or sandbox notice appended.
|
|
69
|
-
*/
|
|
70
|
-
export function renderProcessRead(read, sandbox, escalationModes = []) {
|
|
71
|
-
const notices = [];
|
|
72
|
-
if (read.lossy) {
|
|
73
|
-
const paths = [read.stdoutSpillPath, read.stderrSpillPath].filter((path) => path !== undefined);
|
|
74
|
-
notices.push(`[some output was dropped from memory; full output: ${paths.length > 0 ? paths.join(', ') : '(unavailable)'}]`);
|
|
75
|
-
}
|
|
76
|
-
if (sandbox?.runnerFailed) {
|
|
77
|
-
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]`);
|
|
78
|
-
}
|
|
79
|
-
else if (sandbox?.denied) {
|
|
80
|
-
notices.push(sandboxDenialMarker(sandbox.mode));
|
|
81
|
-
if (escalationModes.length > 0) {
|
|
82
|
-
notices.push(escalationHintMarker('command'));
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
if (notices.length === 0)
|
|
86
|
-
return read.delta;
|
|
87
|
-
return `${read.delta}${read.delta.length > 0 && !read.delta.endsWith('\n') ? '\n' : ''}${notices.join('\n')}`;
|
|
88
|
-
}
|
|
89
60
|
/**
|
|
90
61
|
* The exit-status parse is the shared marker-contract half of the shell-tool
|
|
91
62
|
* rendering story, owned by `@deepseek-ai/dsh-shell` so `dsh-tool-pwsh` reuses
|
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The WSL bash tool instance:
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* The WSL bash tool instance: owns a {@link WslBashExecutor} outright and
|
|
3
|
+
* drives it directly. dsh 0.1.7 removed the shell seam's routing field
|
|
4
|
+
* (`ShellExecRequest.shell`), so there is no shared selector to route through
|
|
5
|
+
* any more — the tool itself is what knows it wants WSL.
|
|
6
|
+
* @module dsh-win-multi-bash/tool-bash/wsl-bash
|
|
5
7
|
*/
|
|
8
|
+
import { WslBashExecutor } from "../../bash-wsl/index.js";
|
|
6
9
|
import { defineShellTool } from "./factory.js";
|
|
7
|
-
/** The WSL bash tool:
|
|
8
|
-
const tool = defineShellTool({
|
|
10
|
+
/** The WSL bash tool: owns the wsl-bash backend. */
|
|
11
|
+
const tool = defineShellTool({
|
|
12
|
+
toolName: 'wsl_bash',
|
|
13
|
+
Executor: WslBashExecutor,
|
|
14
|
+
configKey: 'wslBash',
|
|
15
|
+
dialect: 'wsl',
|
|
16
|
+
});
|
|
9
17
|
export const name = tool.name;
|
|
10
18
|
export const inject = tool.inject;
|
|
11
19
|
export const Config = tool.Config;
|
|
12
20
|
export const apply = tool.apply;
|
|
13
|
-
//# sourceMappingURL=wsl-bash.js.map
|
|
21
|
+
//# sourceMappingURL=wsl-bash.js.map
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-win-multi-bash",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"author": "Dinosaur_MC",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/Dinosaur-MC/dsh-win-multi-bash.git"
|
|
8
8
|
},
|
|
9
|
-
"description": "Windows multi-bash plugin for dsh: git_bash / wsl_bash model tools
|
|
9
|
+
"description": "Windows multi-bash plugin for dsh: git_bash / wsl_bash model tools, each owning its own Git Bash / WSL executor and leaving the ctx.shell seat to dsh's own pwsh executor.",
|
|
10
10
|
"keywords": [
|
|
11
11
|
"dsh-plugin",
|
|
12
12
|
"windows",
|
|
@@ -26,7 +26,6 @@
|
|
|
26
26
|
"main": "lib/index.js",
|
|
27
27
|
"exports": {
|
|
28
28
|
".": "./lib/index.js",
|
|
29
|
-
"./shell-select": "./lib/shell-select/index.js",
|
|
30
29
|
"./tool-git-bash": "./lib/tool-bash/types/git-bash.js",
|
|
31
30
|
"./tool-wsl-bash": "./lib/tool-bash/types/wsl-bash.js",
|
|
32
31
|
"./package.json": "./package.json"
|
|
@@ -50,14 +49,11 @@
|
|
|
50
49
|
},
|
|
51
50
|
"peerDependencies": {
|
|
52
51
|
"@deepseek-ai/cordis": ">=4.0.1",
|
|
53
|
-
"@deepseek-ai/dsh-bash-local": ">=0.1.
|
|
54
|
-
"@deepseek-ai/dsh-llm": ">=0.1.
|
|
55
|
-
"@deepseek-ai/dsh-
|
|
56
|
-
"@deepseek-ai/dsh-
|
|
57
|
-
"@deepseek-ai/dsh-
|
|
58
|
-
"@deepseek-ai/dsh-settings": ">=0.1.2-alpha.2",
|
|
59
|
-
"@deepseek-ai/dsh-shell": ">=0.1.0-rc.7",
|
|
60
|
-
"@deepseek-ai/dsh-tools": ">=0.1.0-rc.7",
|
|
52
|
+
"@deepseek-ai/dsh-bash-local": ">=0.1.7-rc.2",
|
|
53
|
+
"@deepseek-ai/dsh-llm": ">=0.1.7-rc.2",
|
|
54
|
+
"@deepseek-ai/dsh-sandbox": ">=0.1.7-rc.2",
|
|
55
|
+
"@deepseek-ai/dsh-shell": ">=0.1.7-rc.2",
|
|
56
|
+
"@deepseek-ai/dsh-tools": ">=0.1.7-rc.2",
|
|
61
57
|
"@deepseek-ai/schemastery": ">=3.18.0"
|
|
62
58
|
},
|
|
63
59
|
"peerDependenciesMeta": {
|
|
@@ -70,18 +66,9 @@
|
|
|
70
66
|
"@deepseek-ai/dsh-llm": {
|
|
71
67
|
"optional": true
|
|
72
68
|
},
|
|
73
|
-
"@deepseek-ai/dsh-pwsh-local": {
|
|
74
|
-
"optional": true
|
|
75
|
-
},
|
|
76
|
-
"@deepseek-ai/dsh-pwsh-sandbox": {
|
|
77
|
-
"optional": true
|
|
78
|
-
},
|
|
79
69
|
"@deepseek-ai/dsh-sandbox": {
|
|
80
70
|
"optional": true
|
|
81
71
|
},
|
|
82
|
-
"@deepseek-ai/dsh-settings": {
|
|
83
|
-
"optional": true
|
|
84
|
-
},
|
|
85
72
|
"@deepseek-ai/dsh-shell": {
|
|
86
73
|
"optional": true
|
|
87
74
|
},
|
|
@@ -1,23 +0,0 @@
|
|
|
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 };
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
//#region lib/types/invariant.js
|
|
2
|
-
/**
|
|
3
|
-
* Package-owned invariant companion for `@deepseek-ai/dsh-bash-wsl`.
|
|
4
|
-
* @module @deepseek-ai/dsh-bash-wsl/invariant
|
|
5
|
-
*/
|
|
6
|
-
const PACKAGE_NAME = "@deepseek-ai/dsh-bash-wsl";
|
|
7
|
-
/** Cordis companion plugin name. */
|
|
8
|
-
const name = "bash-wsl-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 };
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
import z from "@deepseek-ai/schemastery";
|
|
2
|
-
import { HarnessError } from "@deepseek-ai/dsh-llm";
|
|
3
|
-
import { SHELL_SETTINGS_NAMESPACE, ShellExecutor } from "@deepseek-ai/dsh-shell";
|
|
4
|
-
import { GitBashExecutor } from "../bash-git/index.js";
|
|
5
|
-
import { WslBashExecutor } from "../bash-wsl/index.js";
|
|
6
|
-
import { SandboxPwshExecutor } from "@deepseek-ai/dsh-pwsh-sandbox";
|
|
7
|
-
import { assertServiceableBashConfig } from "@deepseek-ai/dsh-bash-local";
|
|
8
|
-
import { assertServiceablePwshConfig } from "@deepseek-ai/dsh-pwsh-local";
|
|
9
|
-
//#region lib/types/index.js
|
|
10
|
-
/**
|
|
11
|
-
* Selector Service Provider for the bash capability seam: occupies the single
|
|
12
|
-
* `ctx.shell` seat and routes each request to one of several backends held as
|
|
13
|
-
* plain instances. The tool name selects the backend (`request.shell`); a
|
|
14
|
-
* request without one goes to the configured `default`. Backends are
|
|
15
|
-
* constructed on their own child fibers with isolated `shell`/`settings`
|
|
16
|
-
* scopes so their inherited Service registration and settings wiring cannot
|
|
17
|
-
* collide with this executor's own seat. win32 compositions only; POSIX
|
|
18
|
-
* compositions keep a single backend directly on the seat.
|
|
19
|
-
* @module @deepseek-ai/dsh-shell-select
|
|
20
|
-
*/
|
|
21
|
-
/** Error code for routing to an unknown or disabled backend. */
|
|
22
|
-
const SHELL_BACKEND_UNAVAILABLE = "SHELL_BACKEND_UNAVAILABLE";
|
|
23
|
-
const DEFAULT_FACTORIES = {
|
|
24
|
-
"git-bash": (ctx, config) => new GitBashExecutor(ctx, GitBashExecutor.Config(config)),
|
|
25
|
-
"wsl-bash": (ctx, config) => new WslBashExecutor(ctx, WslBashExecutor.Config(config)),
|
|
26
|
-
pwsh: (ctx, config) => new SandboxPwshExecutor(ctx, SandboxPwshExecutor.Config(config))
|
|
27
|
-
};
|
|
28
|
-
/**
|
|
29
|
-
* Selector executor over the bash capability seam. Registers as `ctx.shell`
|
|
30
|
-
* (the single seat), routes every request to one enabled backend by name, and
|
|
31
|
-
* stamps the chosen name on the resolved spec as `shellBackend`. Enabled
|
|
32
|
-
* names without a factory fail loud at the first rebuild; routing to an
|
|
33
|
-
* unbuilt name throws `SHELL_BACKEND_UNAVAILABLE`.
|
|
34
|
-
*/
|
|
35
|
-
var ShellSelectExecutor = class ShellSelectExecutor extends ShellExecutor {
|
|
36
|
-
static inject = [
|
|
37
|
-
"subprocess",
|
|
38
|
-
"sandbox",
|
|
39
|
-
"sandboxPolicy"
|
|
40
|
-
];
|
|
41
|
-
static Config = z.object({
|
|
42
|
-
backends: z.array(z.string()).default([
|
|
43
|
-
"git-bash",
|
|
44
|
-
"wsl-bash",
|
|
45
|
-
"pwsh"
|
|
46
|
-
]),
|
|
47
|
-
default: z.string().default("pwsh"),
|
|
48
|
-
gitBash: GitBashExecutor.Config.default({}),
|
|
49
|
-
wslBash: WslBashExecutor.Config.default({}),
|
|
50
|
-
pwsh: SandboxPwshExecutor.Config.default({})
|
|
51
|
-
});
|
|
52
|
-
/** Test hook: replace backend construction wholesale (mirrors the sandbox-local internals pattern). */
|
|
53
|
-
internals = {};
|
|
54
|
-
/** The currently authoritative config: the settings section, or the composition entry. */
|
|
55
|
-
source;
|
|
56
|
-
backends = /* @__PURE__ */ new Map();
|
|
57
|
-
fibers = [];
|
|
58
|
-
constructor(ctx, config) {
|
|
59
|
-
super(ctx);
|
|
60
|
-
const entry = config;
|
|
61
|
-
assertServiceableBashConfig(entry.gitBash);
|
|
62
|
-
assertServiceableBashConfig(entry.wslBash);
|
|
63
|
-
assertServiceablePwshConfig(entry.pwsh);
|
|
64
|
-
this.source = () => entry;
|
|
65
|
-
ctx.inject(["settings"], (settingsCtx) => {
|
|
66
|
-
settingsCtx.settings.installSection(ctx, SHELL_SETTINGS_NAMESPACE, ShellSelectExecutor.Config, entry, {
|
|
67
|
-
validate: (value) => {
|
|
68
|
-
const v = value;
|
|
69
|
-
assertServiceableBashConfig(v.gitBash);
|
|
70
|
-
assertServiceableBashConfig(v.wslBash);
|
|
71
|
-
assertServiceablePwshConfig(v.pwsh);
|
|
72
|
-
},
|
|
73
|
-
setSource: (current) => {
|
|
74
|
-
this.source = current;
|
|
75
|
-
},
|
|
76
|
-
onChange: () => {
|
|
77
|
-
this.rebuildBackends(this.config);
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
/** Validated config (schemastery applied the defaults before construction). */
|
|
83
|
-
get config() {
|
|
84
|
-
return this.source();
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* The first sandbox mode any enabled backend declares (tool-layer
|
|
88
|
-
* advertisement). One backend's probe/verdict failure (e.g. explicit
|
|
89
|
-
* `sandbox: bwrap` without bubblewrap installed) must not brick the whole
|
|
90
|
-
* selector: that backend advertises no mode here and its error resurfaces
|
|
91
|
-
* loudly at its first routed command — the documented lazy contract.
|
|
92
|
-
*/
|
|
93
|
-
get sandboxMode() {
|
|
94
|
-
this.ensureBackends();
|
|
95
|
-
for (const name of this.config.backends) {
|
|
96
|
-
let mode;
|
|
97
|
-
try {
|
|
98
|
-
mode = this.backends.get(name)?.sandboxMode;
|
|
99
|
-
} catch {
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
if (mode !== void 0) return mode;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
resolve(request) {
|
|
106
|
-
this.ensureBackends();
|
|
107
|
-
const name = request.shell ?? this.config.default;
|
|
108
|
-
return {
|
|
109
|
-
...this.requireBackend(name).resolve(request),
|
|
110
|
-
shellBackend: name
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
run(spec) {
|
|
114
|
-
this.ensureBackends();
|
|
115
|
-
return this.requireBackend(spec.shellBackend ?? this.config.default).run(spec);
|
|
116
|
-
}
|
|
117
|
-
start(spec) {
|
|
118
|
-
this.ensureBackends();
|
|
119
|
-
return this.requireBackend(spec.shellBackend ?? this.config.default).start(spec);
|
|
120
|
-
}
|
|
121
|
-
requireBackend(name) {
|
|
122
|
-
const backend = this.backends.get(name);
|
|
123
|
-
if (backend === void 0) throw new HarnessError(`shell-select: backend "${name}" is not enabled (enabled: ${[...this.backends.keys()].join(", ")})`, SHELL_BACKEND_UNAVAILABLE);
|
|
124
|
-
return backend;
|
|
125
|
-
}
|
|
126
|
-
/** The first rebuild runs at first use, after the internals hook is installed. */
|
|
127
|
-
ensureBackends() {
|
|
128
|
-
if (this.backends.size > 0) return;
|
|
129
|
-
this.rebuildBackends(this.config);
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Construct one backend per enabled name on its own child fiber with
|
|
133
|
-
* isolated shell/settings scopes (see the class doc), or throw for an
|
|
134
|
-
* unknown name. Old fibers are disposed first so rebuilds never leak.
|
|
135
|
-
*/
|
|
136
|
-
rebuildBackends(entry) {
|
|
137
|
-
for (const fiber of this.fibers.splice(0)) fiber.dispose();
|
|
138
|
-
this.backends.clear();
|
|
139
|
-
for (const name of entry.backends) {
|
|
140
|
-
const factory = this.internals.factories?.[name] ?? DEFAULT_FACTORIES[name];
|
|
141
|
-
if (factory === void 0) throw new Error(`shell-select: unknown backend "${name}" (enabled: ${entry.backends.join(", ")})`);
|
|
142
|
-
const fiber = this.ctx.plugin({
|
|
143
|
-
inject: [
|
|
144
|
-
"subprocess",
|
|
145
|
-
"sandbox",
|
|
146
|
-
"sandboxPolicy"
|
|
147
|
-
],
|
|
148
|
-
apply: () => {}
|
|
149
|
-
});
|
|
150
|
-
this.fibers.push(fiber);
|
|
151
|
-
const backendCtx = fiber.ctx.isolate("shell", Symbol(name)).isolate("settings", Symbol(name));
|
|
152
|
-
this.backends.set(name, factory(backendCtx, backendConfig(name, entry)));
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
|
-
/**
|
|
157
|
-
* The config partition for one backend name. Names without a dedicated
|
|
158
|
-
* partition (test-hook factories) receive an empty config; the owning factory
|
|
159
|
-
* decides what it needs.
|
|
160
|
-
*/
|
|
161
|
-
function backendConfig(name, entry) {
|
|
162
|
-
switch (name) {
|
|
163
|
-
case "git-bash": return entry.gitBash;
|
|
164
|
-
case "wsl-bash": return entry.wslBash;
|
|
165
|
-
case "pwsh": return entry.pwsh;
|
|
166
|
-
default: return {};
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
//#endregion
|
|
170
|
-
export { SHELL_BACKEND_UNAVAILABLE, ShellSelectExecutor, ShellSelectExecutor as default };
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
//#region lib/types/invariant.js
|
|
2
|
-
/**
|
|
3
|
-
* Package-owned invariant companion for `@deepseek-ai/dsh-shell-select`.
|
|
4
|
-
* @module @deepseek-ai/dsh-shell-select/invariant
|
|
5
|
-
*/
|
|
6
|
-
const PACKAGE_NAME = "@deepseek-ai/dsh-shell-select";
|
|
7
|
-
/** Cordis companion plugin name. */
|
|
8
|
-
const name = "shell-select-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
|
|
13
|
-
* mutable data relation beyond contracts enforced at its owning seams.
|
|
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 };
|