dsh-win-multi-bash 0.1.2 → 0.3.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/README.i18n.yaml +2 -2
- package/README.md +33 -21
- package/README.zh.md +32 -20
- 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 +55 -19
- 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 -169
- 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
|
@@ -13,8 +13,9 @@ import { defineTool, TOOL_ABORTED } from '@deepseek-ai/dsh-tools';
|
|
|
13
13
|
import { HarnessError } from '@deepseek-ai/dsh-llm';
|
|
14
14
|
import { ESCALATION_TARGETS, approveEscalation, canonicalPath, validateEscalationArgs } from '@deepseek-ai/dsh-sandbox';
|
|
15
15
|
import { DSH_ENV_PREFIX } from '@deepseek-ai/dsh-shell';
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
16
|
+
import { ownExecutor } from "./backend.js";
|
|
17
|
+
import { processJob, processOutcome, processSources } from "./background.js";
|
|
18
|
+
import { parseExitStatus, renderResult } from "./render.js";
|
|
18
19
|
const DIALECT_FACTS = {
|
|
19
20
|
posix: { shell: 'bash', invoke: 'bash -c', paths: 'POSIX paths', env: '$VAR', toolchain: 'the full Unix toolchain' },
|
|
20
21
|
msys: {
|
|
@@ -31,10 +32,10 @@ const DIALECT_FACTS = {
|
|
|
31
32
|
env: '$VAR',
|
|
32
33
|
},
|
|
33
34
|
};
|
|
34
|
-
/**
|
|
35
|
-
|
|
35
|
+
/** The tool-layer keys every shell tool instance carries, before its backend partition. */
|
|
36
|
+
const TOOL_CONFIG = {
|
|
36
37
|
enableRunInBackground: z.boolean().default(true),
|
|
37
|
-
}
|
|
38
|
+
};
|
|
38
39
|
/**
|
|
39
40
|
* The model-facing description of one shell tool instance. The POSIX variant
|
|
40
41
|
* keeps the legacy wording byte-for-byte (the ACP/headless tool-schema
|
|
@@ -197,18 +198,47 @@ const BACKGROUND_OUTPUT_PROPERTIES = {
|
|
|
197
198
|
* tool name, the approval subject, the prompt section name (`tool:<toolName>`),
|
|
198
199
|
* and the `ctx.jobs` kind; the instance module declares its kind in
|
|
199
200
|
* {@link JobKindMap} via declaration merging.
|
|
201
|
+
*
|
|
202
|
+
* The tool owns its executor outright (see `./backend.js`): dsh 0.1.7 dropped
|
|
203
|
+
* the shell seam's routing field, so a tool can no longer ask a shared seat
|
|
204
|
+
* for a-backend-by-name. `def.Executor` is constructed once per tool load and
|
|
205
|
+
* driven directly, leaving `ctx.shell` to the base bundle's own executor.
|
|
200
206
|
* @param def - the instance definition: `toolName` (also the job kind),
|
|
201
|
-
* `
|
|
207
|
+
* `dialect`, `Executor` (the backend class to own), and `configKey` (the
|
|
208
|
+
* Config partition that backend's settings live under).
|
|
202
209
|
* @returns the complete plugin object (name/inject/Config/apply).
|
|
203
210
|
*/
|
|
204
211
|
export function defineShellTool(def) {
|
|
212
|
+
const Config = z.object({
|
|
213
|
+
...TOOL_CONFIG,
|
|
214
|
+
[def.configKey]: def.Executor.Config.default({}),
|
|
215
|
+
});
|
|
205
216
|
return {
|
|
206
217
|
name: `tool-${def.toolName}`,
|
|
207
|
-
|
|
218
|
+
// The last three are the owned executor's own requirements; injecting
|
|
219
|
+
// them here is what lets the backend be built during apply.
|
|
220
|
+
inject: ['tools', 'systemPrompt', 'shellEnv', 'subprocess', 'sandbox', 'sandboxPolicy'],
|
|
208
221
|
Config,
|
|
209
|
-
apply(ctx, config = {}) {
|
|
222
|
+
async apply(ctx, config = {}) {
|
|
210
223
|
const backgroundEnabled = config.enableRunInBackground ?? true;
|
|
211
|
-
const
|
|
224
|
+
const { executor } = ownExecutor(ctx, def.Executor, config[def.configKey]);
|
|
225
|
+
// The confinement probe is async on 0.1.7 (the provider's `confine`
|
|
226
|
+
// returns a promise), so the escalation surface settles before the
|
|
227
|
+
// tool registers: a tool never advertises a confinement it cannot
|
|
228
|
+
// reach.
|
|
229
|
+
//
|
|
230
|
+
// A backend whose probe fails *loud* is deliberately swallowed here
|
|
231
|
+
// — that is the documented lazy contract: an explicit
|
|
232
|
+
// `sandbox: bwrap` without bubblewrap must not brick the tool row
|
|
233
|
+
// at load. It advertises no confinement now and its error
|
|
234
|
+
// resurfaces at the first routed command.
|
|
235
|
+
let defaultMode;
|
|
236
|
+
try {
|
|
237
|
+
defaultMode = await executor.resolveSandboxMode();
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
defaultMode = undefined;
|
|
241
|
+
}
|
|
212
242
|
const escalationModes = defaultMode === undefined ? [] : ESCALATION_TARGETS;
|
|
213
243
|
const sandboxPolicy = defaultMode === undefined ? undefined : ctx.get('sandboxPolicy');
|
|
214
244
|
if (defaultMode !== undefined && sandboxPolicy === undefined) {
|
|
@@ -350,7 +380,6 @@ export function defineShellTool(def) {
|
|
|
350
380
|
command: args.command,
|
|
351
381
|
...workdir !== undefined ? { workdir } : {},
|
|
352
382
|
...args.timeoutMs !== undefined ? { timeoutMs: args.timeoutMs } : {},
|
|
353
|
-
...def.shell !== undefined ? { shell: def.shell } : {},
|
|
354
383
|
dshEnv,
|
|
355
384
|
...policy !== undefined ? { sandboxPolicy: policy } : {},
|
|
356
385
|
};
|
|
@@ -369,26 +398,33 @@ export function defineShellTool(def) {
|
|
|
369
398
|
error.name = 'AbortError';
|
|
370
399
|
throw error;
|
|
371
400
|
}
|
|
401
|
+
// No deadline for a background command, and the process is
|
|
402
|
+
// spawned inside the starter, after the registry admitted it,
|
|
403
|
+
// so the pull sources bind lazily through the `proc` closure.
|
|
404
|
+
const spec = executor.resolve({ ...request, onExpiry: 'none' });
|
|
405
|
+
let proc;
|
|
372
406
|
// Task preflight finishes before the starter can spawn a process.
|
|
373
407
|
const id = jobs.start({
|
|
374
408
|
kind: def.toolName,
|
|
375
409
|
label: args.command,
|
|
376
|
-
...exec.agent ? { owner: exec.agent } : {},
|
|
410
|
+
...exec.agent ? { owner: exec.agent.id } : {},
|
|
411
|
+
output: processSources(() => proc),
|
|
377
412
|
run: () => {
|
|
378
|
-
const
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
};
|
|
413
|
+
const hooks = processJob(async (signal) => {
|
|
414
|
+
proc = await executor.execute({ ...spec, signal });
|
|
415
|
+
return proc;
|
|
416
|
+
}, (started) => processOutcome(started));
|
|
417
|
+
return { done: hooks.done, cancel: (reason) => hooks.cancel(reason) };
|
|
384
418
|
},
|
|
385
419
|
});
|
|
386
420
|
return { kind: 'background', jobId: id };
|
|
387
421
|
}
|
|
388
|
-
|
|
422
|
+
// Foreground is a property of awaiting the handle's result, not
|
|
423
|
+
// of the call: the same `execute` also serves `run_in_background`.
|
|
424
|
+
const result = await (await executor.execute(executor.resolve({
|
|
389
425
|
...request,
|
|
390
426
|
signal: exec.signal,
|
|
391
|
-
}));
|
|
427
|
+
}))).result();
|
|
392
428
|
if (result.aborted) {
|
|
393
429
|
const error = new HarnessError('tool call aborted', TOOL_ABORTED);
|
|
394
430
|
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.0",
|
|
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.0-rc.7",
|
|
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,169 +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 { installSettingsSection } from "@deepseek-ai/dsh-settings";
|
|
5
|
-
import { GitBashExecutor } from "../bash-git/index.js";
|
|
6
|
-
import { WslBashExecutor } from "../bash-wsl/index.js";
|
|
7
|
-
import { SandboxPwshExecutor } from "@deepseek-ai/dsh-pwsh-sandbox";
|
|
8
|
-
import { assertServiceableBashConfig } from "@deepseek-ai/dsh-bash-local";
|
|
9
|
-
import { assertServiceablePwshConfig } from "@deepseek-ai/dsh-pwsh-local";
|
|
10
|
-
//#region lib/types/index.js
|
|
11
|
-
/**
|
|
12
|
-
* Selector Service Provider for the bash capability seam: occupies the single
|
|
13
|
-
* `ctx.shell` seat and routes each request to one of several backends held as
|
|
14
|
-
* plain instances. The tool name selects the backend (`request.shell`); a
|
|
15
|
-
* request without one goes to the configured `default`. Backends are
|
|
16
|
-
* constructed on their own child fibers with isolated `shell`/`settings`
|
|
17
|
-
* scopes so their inherited Service registration and settings wiring cannot
|
|
18
|
-
* collide with this executor's own seat. win32 compositions only; POSIX
|
|
19
|
-
* compositions keep a single backend directly on the seat.
|
|
20
|
-
* @module @deepseek-ai/dsh-shell-select
|
|
21
|
-
*/
|
|
22
|
-
/** Error code for routing to an unknown or disabled backend. */
|
|
23
|
-
const SHELL_BACKEND_UNAVAILABLE = "SHELL_BACKEND_UNAVAILABLE";
|
|
24
|
-
const DEFAULT_FACTORIES = {
|
|
25
|
-
"git-bash": (ctx, config) => new GitBashExecutor(ctx, GitBashExecutor.Config(config)),
|
|
26
|
-
"wsl-bash": (ctx, config) => new WslBashExecutor(ctx, WslBashExecutor.Config(config)),
|
|
27
|
-
pwsh: (ctx, config) => new SandboxPwshExecutor(ctx, SandboxPwshExecutor.Config(config))
|
|
28
|
-
};
|
|
29
|
-
/**
|
|
30
|
-
* Selector executor over the bash capability seam. Registers as `ctx.shell`
|
|
31
|
-
* (the single seat), routes every request to one enabled backend by name, and
|
|
32
|
-
* stamps the chosen name on the resolved spec as `shellBackend`. Enabled
|
|
33
|
-
* names without a factory fail loud at the first rebuild; routing to an
|
|
34
|
-
* unbuilt name throws `SHELL_BACKEND_UNAVAILABLE`.
|
|
35
|
-
*/
|
|
36
|
-
var ShellSelectExecutor = class ShellSelectExecutor extends ShellExecutor {
|
|
37
|
-
static inject = [
|
|
38
|
-
"subprocess",
|
|
39
|
-
"sandbox",
|
|
40
|
-
"sandboxPolicy"
|
|
41
|
-
];
|
|
42
|
-
static Config = z.object({
|
|
43
|
-
backends: z.array(z.string()).default([
|
|
44
|
-
"git-bash",
|
|
45
|
-
"wsl-bash",
|
|
46
|
-
"pwsh"
|
|
47
|
-
]),
|
|
48
|
-
default: z.string().default("pwsh"),
|
|
49
|
-
gitBash: GitBashExecutor.Config.default({}),
|
|
50
|
-
wslBash: WslBashExecutor.Config.default({}),
|
|
51
|
-
pwsh: SandboxPwshExecutor.Config.default({})
|
|
52
|
-
});
|
|
53
|
-
/** Test hook: replace backend construction wholesale (mirrors the sandbox-local internals pattern). */
|
|
54
|
-
internals = {};
|
|
55
|
-
/** The currently authoritative config: the settings section, or the composition entry. */
|
|
56
|
-
source;
|
|
57
|
-
backends = /* @__PURE__ */ new Map();
|
|
58
|
-
fibers = [];
|
|
59
|
-
constructor(ctx, config) {
|
|
60
|
-
super(ctx);
|
|
61
|
-
const entry = config;
|
|
62
|
-
assertServiceableBashConfig(entry.gitBash);
|
|
63
|
-
assertServiceableBashConfig(entry.wslBash);
|
|
64
|
-
assertServiceablePwshConfig(entry.pwsh);
|
|
65
|
-
this.source = () => entry;
|
|
66
|
-
installSettingsSection(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
|
-
/** Validated config (schemastery applied the defaults before construction). */
|
|
82
|
-
get config() {
|
|
83
|
-
return this.source();
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* The first sandbox mode any enabled backend declares (tool-layer
|
|
87
|
-
* advertisement). One backend's probe/verdict failure (e.g. explicit
|
|
88
|
-
* `sandbox: bwrap` without bubblewrap installed) must not brick the whole
|
|
89
|
-
* selector: that backend advertises no mode here and its error resurfaces
|
|
90
|
-
* loudly at its first routed command — the documented lazy contract.
|
|
91
|
-
*/
|
|
92
|
-
get sandboxMode() {
|
|
93
|
-
this.ensureBackends();
|
|
94
|
-
for (const name of this.config.backends) {
|
|
95
|
-
let mode;
|
|
96
|
-
try {
|
|
97
|
-
mode = this.backends.get(name)?.sandboxMode;
|
|
98
|
-
} catch {
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
if (mode !== void 0) return mode;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
resolve(request) {
|
|
105
|
-
this.ensureBackends();
|
|
106
|
-
const name = request.shell ?? this.config.default;
|
|
107
|
-
return {
|
|
108
|
-
...this.requireBackend(name).resolve(request),
|
|
109
|
-
shellBackend: name
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
run(spec) {
|
|
113
|
-
this.ensureBackends();
|
|
114
|
-
return this.requireBackend(spec.shellBackend ?? this.config.default).run(spec);
|
|
115
|
-
}
|
|
116
|
-
start(spec) {
|
|
117
|
-
this.ensureBackends();
|
|
118
|
-
return this.requireBackend(spec.shellBackend ?? this.config.default).start(spec);
|
|
119
|
-
}
|
|
120
|
-
requireBackend(name) {
|
|
121
|
-
const backend = this.backends.get(name);
|
|
122
|
-
if (backend === void 0) throw new HarnessError(`shell-select: backend "${name}" is not enabled (enabled: ${[...this.backends.keys()].join(", ")})`, SHELL_BACKEND_UNAVAILABLE);
|
|
123
|
-
return backend;
|
|
124
|
-
}
|
|
125
|
-
/** The first rebuild runs at first use, after the internals hook is installed. */
|
|
126
|
-
ensureBackends() {
|
|
127
|
-
if (this.backends.size > 0) return;
|
|
128
|
-
this.rebuildBackends(this.config);
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Construct one backend per enabled name on its own child fiber with
|
|
132
|
-
* isolated shell/settings scopes (see the class doc), or throw for an
|
|
133
|
-
* unknown name. Old fibers are disposed first so rebuilds never leak.
|
|
134
|
-
*/
|
|
135
|
-
rebuildBackends(entry) {
|
|
136
|
-
for (const fiber of this.fibers.splice(0)) fiber.dispose();
|
|
137
|
-
this.backends.clear();
|
|
138
|
-
for (const name of entry.backends) {
|
|
139
|
-
const factory = this.internals.factories?.[name] ?? DEFAULT_FACTORIES[name];
|
|
140
|
-
if (factory === void 0) throw new Error(`shell-select: unknown backend "${name}" (enabled: ${entry.backends.join(", ")})`);
|
|
141
|
-
const fiber = this.ctx.plugin({
|
|
142
|
-
inject: [
|
|
143
|
-
"subprocess",
|
|
144
|
-
"sandbox",
|
|
145
|
-
"sandboxPolicy"
|
|
146
|
-
],
|
|
147
|
-
apply: () => {}
|
|
148
|
-
});
|
|
149
|
-
this.fibers.push(fiber);
|
|
150
|
-
const backendCtx = fiber.ctx.isolate("shell", Symbol(name)).isolate("settings", Symbol(name));
|
|
151
|
-
this.backends.set(name, factory(backendCtx, backendConfig(name, entry)));
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
};
|
|
155
|
-
/**
|
|
156
|
-
* The config partition for one backend name. Names without a dedicated
|
|
157
|
-
* partition (test-hook factories) receive an empty config; the owning factory
|
|
158
|
-
* decides what it needs.
|
|
159
|
-
*/
|
|
160
|
-
function backendConfig(name, entry) {
|
|
161
|
-
switch (name) {
|
|
162
|
-
case "git-bash": return entry.gitBash;
|
|
163
|
-
case "wsl-bash": return entry.wslBash;
|
|
164
|
-
case "pwsh": return entry.pwsh;
|
|
165
|
-
default: return {};
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
//#endregion
|
|
169
|
-
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 };
|