@deepseek-ai/dsh-pwsh-local 0.0.1-rc.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/LICENSE +28 -0
- package/README.i18n.yaml +6 -0
- package/README.md +56 -0
- package/README.zh.md +56 -0
- package/lib/index.js +359 -0
- package/lib/invariant.js +23 -0
- package/lib/types/index.d.ts +112 -0
- package/lib/types/invariant.d.ts +16 -0
- package/lib/types/resolve.d.ts +28 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, DeepSeek
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.i18n.yaml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
|
2
|
+
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
|
+
# after editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write packages/bash/pwsh-local/README.md
|
|
5
|
+
README.md: eb3365b009e3595230e5fb0f616079bd73c55840
|
|
6
|
+
README.zh.md: d79201c756a26bbc343e2b284a803b0cf9aee69b
|
package/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# @deepseek-ai/dsh-pwsh-local
|
|
2
|
+
|
|
3
|
+
English | [中文](README.zh.md)
|
|
4
|
+
|
|
5
|
+
Local PowerShell Service provider for the `@deepseek-ai/dsh-bash` executor seam over the [`@deepseek-ai/dsh-subprocess`](../../subprocess/subprocess/README.md) service: `PwshLocalExecutor` spawns `pwsh -NoLogo -NoProfile -NonInteractive -Command <command>` per call as a managed process through `ctx.subprocess`, and owns everything PowerShell-shaped — executable resolution, command defaulting and caps, timeout/cancel classification, the model-friendly terminal environment, and the model-facing stdout/stderr merge for background reads. Group mechanics (bounded spill-backed output, credential scrub, kill escalation, disposal) are the subprocess service's.
|
|
6
|
+
|
|
7
|
+
The command string rides as ONE argv element to `-Command`: PowerShell itself parses the text, and no intermediate shell exists, so there is no shell-quoting layer to escape (the `bash -c` string domain has no equivalent here). Native Win32 paths (`C:\...`) pass through unchanged.
|
|
8
|
+
|
|
9
|
+
The package root exports the default and named `PwshLocalExecutor` plugin, its `Config`, the pure `resolvePwshPath`/`candidatePwshPaths` helpers, and the `ENV_OVERRIDES`/`ENCODING_PREAMBLE` constants the executor injects into every spawn.
|
|
10
|
+
|
|
11
|
+
## Config
|
|
12
|
+
|
|
13
|
+
```yaml
|
|
14
|
+
- id: bash
|
|
15
|
+
name: '@deepseek-ai/dsh-pwsh-local'
|
|
16
|
+
config:
|
|
17
|
+
cwd: C:\path\to\workspace # default: process.cwd()
|
|
18
|
+
timeoutMs: 120000 # default foreground timeout
|
|
19
|
+
maxTimeoutMs: 600000 # cap for per-call overrides
|
|
20
|
+
maxOutputBytes: 64000 # per-stream in-memory cap; overflow spills to disk
|
|
21
|
+
maxSpillBytes: 67108864 # per-stream full-output spill cap
|
|
22
|
+
graceMs: 3000 # kill escalation and post-exit pipe-drain grace
|
|
23
|
+
pwshPath: C:\Program Files\PowerShell\7\pwsh.exe # explicit executable; else well-known locations, then PATH
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Behavior
|
|
27
|
+
|
|
28
|
+
The Windows counterpart of `dsh-bash-local`, deliberately mirroring its semantics call-for-call:
|
|
29
|
+
|
|
30
|
+
- **Spawn per call, no shell state** — every call is a fresh non-interactive `pwsh -Command` (deterministic; no profile files). The `-NoLogo -NoProfile -NonInteractive` flags disable startup banners, profile loading, and prompts that would garble tool output.
|
|
31
|
+
- **UTF-8 output pinned** — every command runs with `[Console]::OutputEncoding` and `$OutputEncoding` set to UTF-8 first, so the Windows PowerShell 5.1 fallback (or any host whose console code page is not UTF-8) cannot garble non-ASCII output: the subprocess collector decodes bytes as UTF-8. Input encoding is left at the host default; pwsh 7 defaults to UTF-8 and is unaffected.
|
|
32
|
+
- **Executable resolution** — `resolvePwshPath` prefers an explicit `pwshPath`, then on Windows probes PowerShell 7's install location, every PATH entry (Microsoft Store installs; surrounding quotes stripped), and Windows PowerShell 5.1 as a legacy last resort, checking `existsSync` on each; elsewhere it falls back to a bare `pwsh` resolved through PATH. Resolution is a pure function of `(configured, env, platform)` and happens once at construction.
|
|
33
|
+
- **Configured budgets over managed groups** — `resolve()` fills `workdir`/`timeoutMs`/`stdoutMaxBytes` from config, and every spawn hands the service explicit byte caps, spill cap, and `graceMs`. The grace must be positive, finite, and no greater than [`MAX_TIMER_DELAY_MS`](../../util/timeout/README.md), so Node can represent it with one timer. Tree termination (taskkill on Windows, process-group signals on POSIX), the post-exit pipe-drain grace, tail-keep truncation, and bounded spill files are [`dsh-subprocess-local`](../../subprocess/subprocess-local/README.md) mechanics. A foreground `BashExecRequest.stdoutMaxBytes` can raise stdout's capture budget for one trusted caller; stderr and background runs still use `maxOutputBytes`.
|
|
34
|
+
- **Timeout and cancel classification** — `run()` fuses its config-clamped timeout with the caller's signal through one deadline; only the executor's own timeout reports `timedOut`, an upstream cancel reports `aborted`, and a self-terminated command reports neither ([timeout-library Agent Note](../../../.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.md)). Windows reports forced termination as exit 1 without a signal, so signal-stamped facts (`signal`, `killed` status) are POSIX-only there; the timeout/abort classification is platform-independent.
|
|
35
|
+
- **Model-friendly terminal env** — `NO_COLOR=1 PAGER=cat GIT_PAGER=cat` (no `TERM=dumb`: that is a POSIX concept; `NO_COLOR` is honored by modern PowerShell renderers) merged as ordinary env under the service's credential scrub and `DSH_*` channel rules; an explicit caller entry still wins.
|
|
36
|
+
- **Background processes** — `start()` returns a live `BashProcess` handle immediately, no timeout applies, and the handle's `readOutput()` merges the service's offset-based stdout/stderr reads into one marked-section delta with a consuming cursor. A still-running process belongs to the subprocess service, so it survives executor reloads and dies (killed and joined) with the service's disposal. Everything task-shaped (ids, ownership, polling, notices) lives in the generic [`ctx.tasks` runtime](../../tasks/tasks/README.md), which the tool layer registers the handle with — this executor never sees a session or a registry.
|
|
37
|
+
|
|
38
|
+
## Model Experience
|
|
39
|
+
|
|
40
|
+
Indirectly, through `dsh-tool-pwsh`, which renders this executor's bounded stdout/stderr tails, background-process deltas (through the generic task runtime), spill-file paths, and infrastructure failures.
|
|
41
|
+
|
|
42
|
+
#### KV Cache effect
|
|
43
|
+
|
|
44
|
+
No direct invalidation; the named consumer owns any request-prefix changes.
|
|
45
|
+
|
|
46
|
+
## Known Limitations and Deferred Work
|
|
47
|
+
|
|
48
|
+
- **Unconfined by itself** — this executor always runs commands with the harness process's authority; deployments needing confinement compose a sandboxing bash executor or policy instead.
|
|
49
|
+
- **No persistent shell or PTY** — every call starts a fresh `pwsh -Command`.
|
|
50
|
+
- **The command string is PowerShell text** — the `-Command` domain has no shell-quoting layer, but a model-facing command is parsed by PowerShell itself, so PowerShell syntax errors are command failures, not launch failures.
|
|
51
|
+
- **A background spawn-failure note is single-delivery** — the subprocess service buffers no output for a process that never ran, so the executor injects `spawn failed: …` into exactly one `readOutput()` delta; a reader that discards that delta cannot recover it.
|
|
52
|
+
- **Windows termination reports no signal** — a force-killed process settles as exit 1 with `signal: null`, so signal-based status classification (POSIX `killed`) does not apply on Windows; `kill()`-initiated stops still stamp `killed` directly.
|
|
53
|
+
- **The encoding preamble precedes the command** — PowerShell requires `param(...)`, `#requires`, and `using namespace`/`using assembly` statements at the very top of a script, so a command whose first statement is one of those cannot run under the UTF-8 output preamble. Wrap a `param(...)` script in `& { … }` (a param block legally heads a script block); `using` statements and `#requires` have no in-command workaround (`#requires` is inert inside `-Command` regardless of position) — run such scripts from a file instead.
|
|
54
|
+
- **Non-ASCII stdin under Windows PowerShell 5.1 may be mis-decoded** — the preamble pins output encoding only; `[Console]::InputEncoding` stays at the host default because setting it under redirected stdin throws. pwsh 7 defaults to UTF-8 and is unaffected.
|
|
55
|
+
|
|
56
|
+
Scrub-heuristic and spill-retention caveats live with [`dsh-subprocess-local`](../../subprocess/subprocess-local/README.md), which owns those mechanics.
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# @deepseek-ai/dsh-pwsh-local
|
|
2
|
+
|
|
3
|
+
[English](README.md) | 中文
|
|
4
|
+
|
|
5
|
+
`@deepseek-ai/dsh-bash` 执行器 seam 的本地 PowerShell Service provider,基于 [`@deepseek-ai/dsh-subprocess`](../../subprocess/subprocess/README.md) 服务:`PwshLocalExecutor` 每次调用以受管进程的方式通过 `ctx.subprocess` spawn `pwsh -NoLogo -NoProfile -NonInteractive -Command <command>`,并拥有所有 PowerShell 形状的职责——可执行文件解析、命令默认化与上限、超时/取消分类、面向模型的终端环境,以及后台读取的 stdout/stderr 合并。进程组机制(有界 spill 输出、凭据清理、终止升级、销毁)属于 subprocess 服务。
|
|
6
|
+
|
|
7
|
+
命令字符串作为 ONE argv 元素传给 `-Command`:由 PowerShell 自己解析文本,不存在中间 shell,因此没有需要转义的 shell 引号层(这里不存在与 `bash -c` 字符串域对应的层)。原生 Win32 路径(`C:\...`)原样通过。
|
|
8
|
+
|
|
9
|
+
包根导出默认与具名 `PwshLocalExecutor` 插件、其 `Config`、纯函数 `resolvePwshPath`/`candidatePwshPaths` 辅助函数,以及执行器注入每次 spawn 的 `ENV_OVERRIDES`/`ENCODING_PREAMBLE` 常量。
|
|
10
|
+
|
|
11
|
+
## 配置
|
|
12
|
+
|
|
13
|
+
```yaml
|
|
14
|
+
- id: bash
|
|
15
|
+
name: '@deepseek-ai/dsh-pwsh-local'
|
|
16
|
+
config:
|
|
17
|
+
cwd: C:\path\to\workspace # default: process.cwd()
|
|
18
|
+
timeoutMs: 120000 # default foreground timeout
|
|
19
|
+
maxTimeoutMs: 600000 # cap for per-call overrides
|
|
20
|
+
maxOutputBytes: 64000 # per-stream in-memory cap; overflow spills to disk
|
|
21
|
+
maxSpillBytes: 67108864 # per-stream full-output spill cap
|
|
22
|
+
graceMs: 3000 # kill escalation and post-exit pipe-drain grace
|
|
23
|
+
pwshPath: C:\Program Files\PowerShell\7\pwsh.exe # explicit executable; else well-known locations, then PATH
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## 行为
|
|
27
|
+
|
|
28
|
+
作为 `dsh-bash-local` 的 Windows 对应物,逐调用地镜像其语义:
|
|
29
|
+
|
|
30
|
+
- **每次调用新建进程,无 shell 状态**——每次调用都是全新的非交互 `pwsh -Command`(确定性;不加载 profile 文件)。`-NoLogo -NoProfile -NonInteractive` 关闭启动横幅、profile 加载与会干扰工具输出的提示符。
|
|
31
|
+
- **UTF-8 输出固定**——每条命令都先以 UTF-8 设置 `[Console]::OutputEncoding` 与 `$OutputEncoding`,因此 Windows PowerShell 5.1 兜底(或任何控制台代码页非 UTF-8 的主机)不会破坏非 ASCII 输出:subprocess collector 以 UTF-8 解码字节。输入编码保持宿主默认;pwsh 7 默认为 UTF-8,不受影响。
|
|
32
|
+
- **可执行文件解析**——`resolvePwshPath` 优先显式 `pwshPath`,然后在 Windows 上依次探测 PowerShell 7 安装位置、每个 PATH 条目(Microsoft Store 安装;剥离两端引号)以及作为遗留兜底的 Windows PowerShell 5.1,逐一检查 `existsSync`;其他平台回退为通过 PATH 解析的裸 `pwsh`。解析是 `(configured, env, platform)` 的纯函数,在构造时执行一次。
|
|
33
|
+
- **受管进程组之上的配置预算**——`resolve()` 从配置填充 `workdir`/`timeoutMs`/`stdoutMaxBytes`,每次 spawn 都向服务提供显式字节上限、spill 上限与 `graceMs`。该宽限期须为正有限值,且不得大于 [`MAX_TIMER_DELAY_MS`](../../util/timeout/README.md),这样 Node 就能用一个定时器表示它。进程树终止(Windows 用 taskkill,POSIX 用进程组信号)、退出后管道排空宽限、保尾截断与有界 spill 文件是 [`dsh-subprocess-local`](../../subprocess/subprocess-local/README.md) 的机制。前台 `BashExecRequest.stdoutMaxBytes` 可为单个受信调用方提高 stdout 捕获预算;stderr 与后台运行仍使用 `maxOutputBytes`。
|
|
34
|
+
- **超时与取消分类**——`run()` 通过一个 deadline 融合按配置上限截取的超时与调用方信号;只有执行器自身超时报告 `timedOut`,上游取消报告 `aborted`,自我终止的命令两者都不报告(见 [timeout 库 Agent Note](../../../.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.md))。Windows 将强制终止报告为退出码 1 且无信号,因此带信号标记的事实(`signal`、`killed` 状态)在那里仅限 POSIX;超时/取消分类与平台无关。
|
|
35
|
+
- **面向模型的终端环境**——`NO_COLOR=1 PAGER=cat GIT_PAGER=cat`(没有 `TERM=dumb`:那是 POSIX 概念;现代 PowerShell 渲染器遵循 `NO_COLOR`),作为普通 env 在服务的凭据清理与 `DSH_*` 通道规则之下合并;显式调用方条目仍然优先。
|
|
36
|
+
- **后台进程**——`start()` 立即返回存活的 `BashProcess` 句柄,不设超时;句柄的 `readOutput()` 把服务基于偏移的 stdout/stderr 读取合并为带标记分段的增量与消费游标。仍在运行的进程属于 subprocess 服务,因此它跨执行器重载存活,并随服务销毁(被终止并 join)。一切任务形状的职责(id、所有权、轮询、通知)都在通用 [`ctx.tasks` 运行时](../../tasks/tasks/README.md) 中,由工具层把句柄注册进去——本执行器从不接触会话或注册表。
|
|
37
|
+
|
|
38
|
+
## 模型体验
|
|
39
|
+
|
|
40
|
+
间接地,经由 `dsh-tool-pwsh` 呈现本执行器的有界 stdout/stderr 尾部、后台进程增量(经通用任务运行时)、spill 文件路径与基础设施失败。
|
|
41
|
+
|
|
42
|
+
#### KV Cache 影响
|
|
43
|
+
|
|
44
|
+
无直接失效;具名消费方拥有请求前缀的任何变更。
|
|
45
|
+
|
|
46
|
+
## 已知局限与延期工作
|
|
47
|
+
|
|
48
|
+
- **自身不设沙箱**——本执行器始终以 harness 进程的权限运行命令;需要约束的部署应组合沙箱化 bash 执行器或策略。
|
|
49
|
+
- **无持久 shell 或 PTY**——每次调用都是全新的 `pwsh -Command`。
|
|
50
|
+
- **命令字符串是 PowerShell 文本**——`-Command` 域没有 shell 引号层,但面向模型的命令由 PowerShell 自己解析,因此 PowerShell 语法错误是命令失败,而非启动失败。
|
|
51
|
+
- **后台 spawn 失败提示只投递一次**——subprocess 服务不会为从未运行的进程缓冲输出,因此执行器只把 `spawn failed: …` 注入一次 `readOutput()` 增量;丢弃该增量的读取方无法恢复它。
|
|
52
|
+
- **Windows 终止不报告信号**——被强制终止的进程以退出码 1、`signal: null` 结束,因此基于信号的状态分类(POSIX `killed`)在 Windows 上不适用;`kill()` 发起的停止仍会直接盖上 `killed`。
|
|
53
|
+
- **编码 preamble 位于命令之前**——PowerShell 要求 `param(...)`、`#requires` 与 `using namespace`/`using assembly` 语句位于脚本最顶部,因此以其中一种开头的命令无法在 UTF-8 输出 preamble 下运行。`param(...)` 脚本可包进 `& { … }`(param 块可以合法地位于脚本块开头);`using` 语句与 `#requires` 在命令内没有变通办法(`#requires` 在 `-Command` 中无论位置如何都不生效)——此类脚本请改从文件运行。
|
|
54
|
+
- **Windows PowerShell 5.1 下的非 ASCII stdin 可能被错误解码**——preamble 只固定输出编码;`[Console]::InputEncoding` 保持主机默认,因为在重定向 stdin 下设置它会抛出异常。pwsh 7 默认 UTF-8,不受影响。
|
|
55
|
+
|
|
56
|
+
清理启发式与 spill 保留的注意事项由 [`dsh-subprocess-local`](../../subprocess/subprocess-local/README.md) 持有,它拥有这些机制。
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import z from "@deepseek-ai/schemastery";
|
|
2
|
+
import { BashExecutor } from "@deepseek-ai/dsh-bash";
|
|
3
|
+
import { MAX_TIMER_DELAY_MS, clampTimeout, deadline, timeoutOf } from "@deepseek-ai/dsh-timeout";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
//#region lib/types/resolve.js
|
|
7
|
+
/**
|
|
8
|
+
* PowerShell executable resolution, dependency-free so non-package consumers
|
|
9
|
+
* (the repository's coverage-gate probe in `vitest.config.ts`) can share the
|
|
10
|
+
* ONE resolution definition with the executor and its suites — a probe that
|
|
11
|
+
* resolved differently from the code under test could exempt a file whose
|
|
12
|
+
* suites actually run.
|
|
13
|
+
*
|
|
14
|
+
* @module @deepseek-ai/dsh-pwsh-local/resolve
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Well-known Windows PowerShell install locations plus PATH entries, newest
|
|
18
|
+
* first. Explicitly parameterized (env) so resolution is a pure function of
|
|
19
|
+
* its inputs on every platform.
|
|
20
|
+
* @param env - the environment to probe; defaults to the process environment.
|
|
21
|
+
* @returns candidate `pwsh` executable paths in resolution order.
|
|
22
|
+
*/
|
|
23
|
+
function candidatePwshPaths(env = process.env) {
|
|
24
|
+
const programFiles = env.ProgramFiles ?? "C:\\Program Files";
|
|
25
|
+
const systemRoot = env.SystemRoot ?? "C:\\Windows";
|
|
26
|
+
const candidates = [join(programFiles, "PowerShell", "7", "pwsh.exe")];
|
|
27
|
+
for (const entry of (env.PATH ?? "").split(";")) {
|
|
28
|
+
const trimmed = entry.trim().replace(/^"|"$/g, "");
|
|
29
|
+
if (trimmed.length === 0) continue;
|
|
30
|
+
candidates.push(join(trimmed, "pwsh.exe"));
|
|
31
|
+
}
|
|
32
|
+
candidates.push(join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe"));
|
|
33
|
+
return candidates;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Resolve the pwsh executable this executor spawns.
|
|
37
|
+
* @param configured - an explicit `pwshPath` config value, trusted as-is.
|
|
38
|
+
* @param env - the environment to probe on Windows; defaults to the process environment.
|
|
39
|
+
* @param platform - the platform to resolve for; defaults to the process platform.
|
|
40
|
+
* @returns the first existing well-known location on Windows (PowerShell 7
|
|
41
|
+
* install, a PATH entry such as the Microsoft Store install, then Windows
|
|
42
|
+
* PowerShell 5.1), else `pwsh` for PATH resolution.
|
|
43
|
+
*/
|
|
44
|
+
function resolvePwshPath(configured, env = process.env, platform = process.platform) {
|
|
45
|
+
if (configured !== void 0 && configured.length > 0) return configured;
|
|
46
|
+
if (platform === "win32") {
|
|
47
|
+
for (const candidate of candidatePwshPaths(env)) if (existsSync(candidate)) return candidate;
|
|
48
|
+
}
|
|
49
|
+
return "pwsh";
|
|
50
|
+
}
|
|
51
|
+
//#endregion
|
|
52
|
+
//#region lib/types/index.js
|
|
53
|
+
/**
|
|
54
|
+
* Local PowerShell Service provider for the bash capability seam. Each command runs
|
|
55
|
+
* as `pwsh -NoLogo -NoProfile -NonInteractive -Command <command>` in a managed
|
|
56
|
+
* process spawned through `ctx.subprocess`; the executor owns command
|
|
57
|
+
* defaulting, deadlines and cause classification, the model-friendly terminal
|
|
58
|
+
* environment, and the model-facing stdout/stderr merge for background reads.
|
|
59
|
+
*
|
|
60
|
+
* The command string is passed as ONE argv element to `-Command`: PowerShell
|
|
61
|
+
* itself parses the text, and no intermediate shell exists, so there is no
|
|
62
|
+
* shell-quoting layer to escape (the `bash -c` string domain has no
|
|
63
|
+
* equivalent here). Native Win32 paths (`C:\...`) pass through unchanged.
|
|
64
|
+
*
|
|
65
|
+
* @module @deepseek-ai/dsh-pwsh-local
|
|
66
|
+
*/
|
|
67
|
+
var __addDisposableResource = function(env, value, async) {
|
|
68
|
+
if (value !== null && value !== void 0) {
|
|
69
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
70
|
+
var dispose, inner;
|
|
71
|
+
if (async) {
|
|
72
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
73
|
+
dispose = value[Symbol.asyncDispose];
|
|
74
|
+
}
|
|
75
|
+
if (dispose === void 0) {
|
|
76
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
77
|
+
dispose = value[Symbol.dispose];
|
|
78
|
+
if (async) inner = dispose;
|
|
79
|
+
}
|
|
80
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
81
|
+
if (inner) dispose = function() {
|
|
82
|
+
try {
|
|
83
|
+
inner.call(this);
|
|
84
|
+
} catch (e) {
|
|
85
|
+
return Promise.reject(e);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
env.stack.push({
|
|
89
|
+
value,
|
|
90
|
+
dispose,
|
|
91
|
+
async
|
|
92
|
+
});
|
|
93
|
+
} else if (async) env.stack.push({ async: true });
|
|
94
|
+
return value;
|
|
95
|
+
};
|
|
96
|
+
var __disposeResources = (function(SuppressedError) {
|
|
97
|
+
return function(env) {
|
|
98
|
+
function fail(e) {
|
|
99
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
100
|
+
env.hasError = true;
|
|
101
|
+
}
|
|
102
|
+
var r, s = 0;
|
|
103
|
+
function next() {
|
|
104
|
+
while (r = env.stack.pop()) try {
|
|
105
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
106
|
+
if (r.dispose) {
|
|
107
|
+
var result = r.dispose.call(r.value);
|
|
108
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) {
|
|
109
|
+
fail(e);
|
|
110
|
+
return next();
|
|
111
|
+
});
|
|
112
|
+
} else s |= 1;
|
|
113
|
+
} catch (e) {
|
|
114
|
+
fail(e);
|
|
115
|
+
}
|
|
116
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
117
|
+
if (env.hasError) throw env.error;
|
|
118
|
+
}
|
|
119
|
+
return next();
|
|
120
|
+
};
|
|
121
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed, message) {
|
|
122
|
+
var e = new Error(message);
|
|
123
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
124
|
+
});
|
|
125
|
+
/**
|
|
126
|
+
* Model-friendly environment overrides for PowerShell: disable colors and
|
|
127
|
+
* pagers that would garble tool output. `TERM=dumb` is a POSIX concept and is
|
|
128
|
+
* deliberately absent; `NO_COLOR` is honored by modern pwsh renderers.
|
|
129
|
+
*/
|
|
130
|
+
const ENV_OVERRIDES = {
|
|
131
|
+
NO_COLOR: "1",
|
|
132
|
+
PAGER: "cat",
|
|
133
|
+
GIT_PAGER: "cat"
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* UTF-8 output pinning prepended to every command. The subprocess collector
|
|
137
|
+
* decodes output bytes as UTF-8, but Windows PowerShell 5.1 (the last-resort
|
|
138
|
+
* executable fallback) writes the console/OEM code page by default, which
|
|
139
|
+
* garbles non-ASCII output; pwsh 7 defaults to UTF-8 and is unaffected. The
|
|
140
|
+
* statements ride on line 1 after `; ` separators so PowerShell error line
|
|
141
|
+
* numbers stay accurate.
|
|
142
|
+
*/
|
|
143
|
+
const ENCODING_PREAMBLE = "[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false); $OutputEncoding = [System.Text.UTF8Encoding]::new($false); ";
|
|
144
|
+
/** Default SIGTERM→SIGKILL grace period (the `graceMs` config). */
|
|
145
|
+
const DEFAULT_GRACE_MS = 3e3;
|
|
146
|
+
/** Default per-stream spill cap (the `maxSpillBytes` config). */
|
|
147
|
+
const DEFAULT_MAX_SPILL_BYTES = 64 * 1024 * 1024;
|
|
148
|
+
/** Project a settled collect-mode reader into the final CollectedOutput shape. */
|
|
149
|
+
function finalOutput(reader) {
|
|
150
|
+
const read = reader.readFrom(0);
|
|
151
|
+
return {
|
|
152
|
+
text: read.text,
|
|
153
|
+
truncated: read.lossy,
|
|
154
|
+
...read.spillPath !== void 0 ? { spillPath: read.spillPath } : {}
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function assertPositiveFinite(name, value) {
|
|
158
|
+
if (!Number.isFinite(value) || value <= 0) throw new Error(`pwsh-local: ${name} must be a positive finite number`);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Local PowerShell executor over `ctx.subprocess`. Bounded output, spill
|
|
162
|
+
* files, and process-tree termination are the subprocess service's mechanics;
|
|
163
|
+
* this executor supplies their configured budgets per spawn.
|
|
164
|
+
*/
|
|
165
|
+
var PwshLocalExecutor = class PwshLocalExecutor extends BashExecutor {
|
|
166
|
+
static inject = ["subprocess"];
|
|
167
|
+
static Config = z.object({
|
|
168
|
+
cwd: z.string(),
|
|
169
|
+
timeoutMs: z.number().default(12e4),
|
|
170
|
+
maxTimeoutMs: z.number().default(6e5),
|
|
171
|
+
maxOutputBytes: z.number().default(64e3),
|
|
172
|
+
maxSpillBytes: z.number().default(DEFAULT_MAX_SPILL_BYTES),
|
|
173
|
+
graceMs: z.number().default(DEFAULT_GRACE_MS),
|
|
174
|
+
pwshPath: z.string()
|
|
175
|
+
});
|
|
176
|
+
/** Validated config (schemastery applied the defaults before construction). */
|
|
177
|
+
config;
|
|
178
|
+
/** The pwsh executable resolved once at construction. */
|
|
179
|
+
pwshPath;
|
|
180
|
+
constructor(ctx, config) {
|
|
181
|
+
super(ctx);
|
|
182
|
+
this.config = config;
|
|
183
|
+
assertPositiveFinite("timeoutMs", this.config.timeoutMs);
|
|
184
|
+
assertPositiveFinite("maxTimeoutMs", this.config.maxTimeoutMs);
|
|
185
|
+
assertPositiveFinite("maxOutputBytes", this.config.maxOutputBytes);
|
|
186
|
+
assertPositiveFinite("maxSpillBytes", this.config.maxSpillBytes);
|
|
187
|
+
assertPositiveFinite("graceMs", this.config.graceMs);
|
|
188
|
+
if (this.config.graceMs > MAX_TIMER_DELAY_MS) throw new Error(`pwsh-local: graceMs must be no greater than ${MAX_TIMER_DELAY_MS}`);
|
|
189
|
+
this.pwshPath = resolvePwshPath(this.config.pwshPath);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Resolve a request into a fully-specified spec: fill `workdir` from
|
|
193
|
+
* `config.cwd` (else `process.cwd()`), and `timeoutMs` from
|
|
194
|
+
* `config.timeoutMs`, capped at `config.maxTimeoutMs`.
|
|
195
|
+
*/
|
|
196
|
+
resolve(request) {
|
|
197
|
+
const timeoutMs = clampTimeout(request.timeoutMs, this.config.timeoutMs, this.config.maxTimeoutMs, "pwsh-local: request.timeoutMs");
|
|
198
|
+
const stdoutMaxBytes = request.stdoutMaxBytes ?? this.config.maxOutputBytes;
|
|
199
|
+
assertPositiveFinite("request.stdoutMaxBytes", stdoutMaxBytes);
|
|
200
|
+
return {
|
|
201
|
+
command: request.command,
|
|
202
|
+
workdir: request.workdir ?? this.config.cwd ?? process.cwd(),
|
|
203
|
+
timeoutMs,
|
|
204
|
+
stdoutMaxBytes,
|
|
205
|
+
...request.signal ? { signal: request.signal } : {},
|
|
206
|
+
...request.stdin !== void 0 ? { stdin: request.stdin } : {},
|
|
207
|
+
...request.env !== void 0 ? { env: request.env } : {},
|
|
208
|
+
...request.dshEnv !== void 0 ? { dshEnv: request.dshEnv } : {},
|
|
209
|
+
sandboxPolicy: request.sandboxPolicy
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* The pwsh invocation argv for one resolved spec — the argv-level seam a
|
|
214
|
+
* confining subclass wraps through `ctx.sandbox.confine` (the pwsh twin of
|
|
215
|
+
* `dsh-bash-local`'s `runArgv`/`startArgv` hooks; see
|
|
216
|
+
* `@deepseek-ai/dsh-pwsh-sandbox`).
|
|
217
|
+
*/
|
|
218
|
+
argv(spec) {
|
|
219
|
+
return [
|
|
220
|
+
this.pwshPath,
|
|
221
|
+
"-NoLogo",
|
|
222
|
+
"-NoProfile",
|
|
223
|
+
"-NonInteractive",
|
|
224
|
+
"-Command",
|
|
225
|
+
`${ENCODING_PREAMBLE}${spec.command}`
|
|
226
|
+
];
|
|
227
|
+
}
|
|
228
|
+
/** Map one resolved spec plus its argv onto a fully-specified subprocess spawn. */
|
|
229
|
+
spawnSpec(spec, stdoutMaxBytes, signal, argv) {
|
|
230
|
+
const collect = (maxBytes) => ({
|
|
231
|
+
maxBytes,
|
|
232
|
+
spill: { maxBytes: this.config.maxSpillBytes }
|
|
233
|
+
});
|
|
234
|
+
return {
|
|
235
|
+
argv: [...argv],
|
|
236
|
+
cwd: spec.workdir,
|
|
237
|
+
stdio: {
|
|
238
|
+
stdin: spec.stdin !== void 0 ? { data: spec.stdin } : "ignore",
|
|
239
|
+
stdout: collect(stdoutMaxBytes),
|
|
240
|
+
stderr: collect(this.config.maxOutputBytes)
|
|
241
|
+
},
|
|
242
|
+
graceMs: this.config.graceMs,
|
|
243
|
+
signal,
|
|
244
|
+
env: {
|
|
245
|
+
...ENV_OVERRIDES,
|
|
246
|
+
...spec.env,
|
|
247
|
+
...spec.dshEnv
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
/** The collect-mode readers the executor itself requested (present by construction). */
|
|
252
|
+
static collected(handle) {
|
|
253
|
+
const { stdout, stderr } = handle.collected;
|
|
254
|
+
/* v8 ignore start -- collect dispositions expose both readers by the seam contract; defensive. */
|
|
255
|
+
if (stdout === void 0 || stderr === void 0) throw new Error("pwsh-local: subprocess implementation dropped a requested collect stream");
|
|
256
|
+
/* v8 ignore stop */
|
|
257
|
+
return {
|
|
258
|
+
stdout,
|
|
259
|
+
stderr
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
async run(spec) {
|
|
263
|
+
return this.runArgv(spec, this.argv(spec));
|
|
264
|
+
}
|
|
265
|
+
/** Foreground run of an exact argv (the confining subclass re-wraps it). */
|
|
266
|
+
async runArgv(spec, argv) {
|
|
267
|
+
const env_1 = {
|
|
268
|
+
stack: [],
|
|
269
|
+
error: void 0,
|
|
270
|
+
hasError: false
|
|
271
|
+
};
|
|
272
|
+
try {
|
|
273
|
+
const d = __addDisposableResource(env_1, deadline(spec.signal, spec.timeoutMs, "BASH_TIMEOUT"), false);
|
|
274
|
+
const handle = this.ctx.subprocess.spawn(this.spawnSpec(spec, spec.stdoutMaxBytes, d.signal, argv));
|
|
275
|
+
const outcome = await handle.done;
|
|
276
|
+
const collected = PwshLocalExecutor.collected(handle);
|
|
277
|
+
const timedOut = timeoutOf(d.signal, "BASH_TIMEOUT") !== void 0;
|
|
278
|
+
const aborted = d.signal.aborted && !timedOut;
|
|
279
|
+
return {
|
|
280
|
+
...outcome,
|
|
281
|
+
timedOut,
|
|
282
|
+
aborted,
|
|
283
|
+
timeoutMs: spec.timeoutMs,
|
|
284
|
+
stdout: finalOutput(collected.stdout),
|
|
285
|
+
stderr: finalOutput(collected.stderr)
|
|
286
|
+
};
|
|
287
|
+
} catch (e_1) {
|
|
288
|
+
env_1.error = e_1;
|
|
289
|
+
env_1.hasError = true;
|
|
290
|
+
} finally {
|
|
291
|
+
__disposeResources(env_1);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
start(spec) {
|
|
295
|
+
return this.startArgv(spec, this.argv(spec));
|
|
296
|
+
}
|
|
297
|
+
/** Background start of an exact argv (the confining subclass re-wraps it). */
|
|
298
|
+
startArgv(spec, argv) {
|
|
299
|
+
const running = this.ctx.subprocess.spawn(this.spawnSpec(spec, this.config.maxOutputBytes, spec.signal, argv));
|
|
300
|
+
const collected = PwshLocalExecutor.collected(running);
|
|
301
|
+
let spawnFailureNote;
|
|
302
|
+
const consumeSpawnFailure = () => {
|
|
303
|
+
const note = spawnFailureNote ?? "";
|
|
304
|
+
spawnFailureNote = void 0;
|
|
305
|
+
return note;
|
|
306
|
+
};
|
|
307
|
+
let stdoutOffset = 0;
|
|
308
|
+
let stderrOffset = 0;
|
|
309
|
+
const proc = {
|
|
310
|
+
status: "running",
|
|
311
|
+
exitCode: null,
|
|
312
|
+
signal: null,
|
|
313
|
+
done: running.done.then((outcome) => {
|
|
314
|
+
if (proc.status === "running") proc.status = spec.signal?.aborted === true || outcome.signal !== null ? "killed" : "completed";
|
|
315
|
+
proc.exitCode = outcome.exitCode;
|
|
316
|
+
proc.signal = outcome.signal;
|
|
317
|
+
this.onProcessDone(proc, collected.stderr.readFrom(0).text, false);
|
|
318
|
+
}, (error) => {
|
|
319
|
+
proc.status = "killed";
|
|
320
|
+
spawnFailureNote = `spawn failed: ${String(error)}`;
|
|
321
|
+
this.onProcessDone(proc, spawnFailureNote, true, error);
|
|
322
|
+
}),
|
|
323
|
+
readOutput: () => {
|
|
324
|
+
const out = collected.stdout.readFrom(stdoutOffset);
|
|
325
|
+
const err = collected.stderr.readFrom(stderrOffset);
|
|
326
|
+
stdoutOffset = out.nextOffset;
|
|
327
|
+
stderrOffset = err.nextOffset;
|
|
328
|
+
const errText = err.text.length > 0 ? err.text : consumeSpawnFailure();
|
|
329
|
+
const separator = out.text.length > 0 && !out.text.endsWith("\n") ? "\n" : "";
|
|
330
|
+
return {
|
|
331
|
+
delta: out.text + (errText.length > 0 ? `${separator}[stderr]\n${errText}` : ""),
|
|
332
|
+
lossy: out.lossy || err.lossy,
|
|
333
|
+
...out.spillPath !== void 0 ? { stdoutSpillPath: out.spillPath } : {},
|
|
334
|
+
...err.spillPath !== void 0 ? { stderrSpillPath: err.spillPath } : {}
|
|
335
|
+
};
|
|
336
|
+
},
|
|
337
|
+
kill: () => {
|
|
338
|
+
if (proc.status !== "running") return false;
|
|
339
|
+
proc.status = "killed";
|
|
340
|
+
running.terminate();
|
|
341
|
+
return true;
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
return proc;
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Settlement hook for subclasses that attach execution facts to a process.
|
|
348
|
+
* The base implementation is intentionally empty. Mirrored from
|
|
349
|
+
* `dsh-bash-local` (whose sandboxing subclass consumes the same hook); the
|
|
350
|
+
* pwsh-confining consumer is `@deepseek-ai/dsh-pwsh-sandbox`.
|
|
351
|
+
* @param _proc - the settled process handle.
|
|
352
|
+
* @param _stderr - the process's retained stderr tail used by subclasses for settlement classification.
|
|
353
|
+
* @param _spawnFailed - whether the spawn rejected before any process existed.
|
|
354
|
+
* @param _spawnError - the spawn rejection, when `_spawnFailed`.
|
|
355
|
+
*/
|
|
356
|
+
onProcessDone(_proc, _stderr, _spawnFailed, _spawnError) {}
|
|
357
|
+
};
|
|
358
|
+
//#endregion
|
|
359
|
+
export { ENCODING_PREAMBLE, ENV_OVERRIDES, PwshLocalExecutor, PwshLocalExecutor as default, candidatePwshPaths, resolvePwshPath };
|
package/lib/invariant.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region lib/types/invariant.js
|
|
2
|
+
/**
|
|
3
|
+
* Package-owned invariant companion for `@deepseek-ai/dsh-pwsh-local`.
|
|
4
|
+
* @module @deepseek-ai/dsh-pwsh-local/invariant
|
|
5
|
+
*/
|
|
6
|
+
const PACKAGE_NAME = "@deepseek-ai/dsh-pwsh-local";
|
|
7
|
+
/** Cordis companion plugin name. */
|
|
8
|
+
const name = "pwsh-local-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 };
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local PowerShell Service provider for the bash capability seam. Each command runs
|
|
3
|
+
* as `pwsh -NoLogo -NoProfile -NonInteractive -Command <command>` in a managed
|
|
4
|
+
* process spawned through `ctx.subprocess`; the executor owns command
|
|
5
|
+
* defaulting, deadlines and cause classification, the model-friendly terminal
|
|
6
|
+
* environment, and the model-facing stdout/stderr merge for background reads.
|
|
7
|
+
*
|
|
8
|
+
* The command string is passed as ONE argv element to `-Command`: PowerShell
|
|
9
|
+
* itself parses the text, and no intermediate shell exists, so there is no
|
|
10
|
+
* shell-quoting layer to escape (the `bash -c` string domain has no
|
|
11
|
+
* equivalent here). Native Win32 paths (`C:\...`) pass through unchanged.
|
|
12
|
+
*
|
|
13
|
+
* @module @deepseek-ai/dsh-pwsh-local
|
|
14
|
+
*/
|
|
15
|
+
import { Context } from '@deepseek-ai/cordis';
|
|
16
|
+
import z from '@deepseek-ai/schemastery';
|
|
17
|
+
import { BashExecutor } from '@deepseek-ai/dsh-bash';
|
|
18
|
+
import type { BashExecRequest, BashExecSpec, BashProcess, BashRunResult } from '@deepseek-ai/dsh-bash';
|
|
19
|
+
/**
|
|
20
|
+
* Model-friendly environment overrides for PowerShell: disable colors and
|
|
21
|
+
* pagers that would garble tool output. `TERM=dumb` is a POSIX concept and is
|
|
22
|
+
* deliberately absent; `NO_COLOR` is honored by modern pwsh renderers.
|
|
23
|
+
*/
|
|
24
|
+
export declare const ENV_OVERRIDES: {
|
|
25
|
+
readonly NO_COLOR: "1";
|
|
26
|
+
readonly PAGER: "cat";
|
|
27
|
+
readonly GIT_PAGER: "cat";
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* UTF-8 output pinning prepended to every command. The subprocess collector
|
|
31
|
+
* decodes output bytes as UTF-8, but Windows PowerShell 5.1 (the last-resort
|
|
32
|
+
* executable fallback) writes the console/OEM code page by default, which
|
|
33
|
+
* garbles non-ASCII output; pwsh 7 defaults to UTF-8 and is unaffected. The
|
|
34
|
+
* statements ride on line 1 after `; ` separators so PowerShell error line
|
|
35
|
+
* numbers stay accurate.
|
|
36
|
+
*/
|
|
37
|
+
export declare const ENCODING_PREAMBLE = "[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false); $OutputEncoding = [System.Text.UTF8Encoding]::new($false); ";
|
|
38
|
+
/** Plugin config (all optional — `static Config` supplies the defaults). */
|
|
39
|
+
export interface Config {
|
|
40
|
+
/** Default working directory for commands (default: process.cwd()). */
|
|
41
|
+
cwd?: string;
|
|
42
|
+
/** Default foreground timeout in milliseconds. */
|
|
43
|
+
timeoutMs?: number;
|
|
44
|
+
/** Upper bound for per-call timeout overrides. */
|
|
45
|
+
maxTimeoutMs?: number;
|
|
46
|
+
/** Per-stream in-memory output cap; overflow spills to a temp file. */
|
|
47
|
+
maxOutputBytes?: number;
|
|
48
|
+
/** Per-stream spill-file cap; larger streams retain only their in-memory tail. */
|
|
49
|
+
maxSpillBytes?: number;
|
|
50
|
+
/** Grace period for kill escalation and inherited pipes; at most `MAX_TIMER_DELAY_MS`. */
|
|
51
|
+
graceMs?: number;
|
|
52
|
+
/**
|
|
53
|
+
* Explicit pwsh executable. When omitted, well-known Windows install
|
|
54
|
+
* locations and PATH entries are probed in order (PowerShell 7 install,
|
|
55
|
+
* PATH entries such as the Microsoft Store install, then Windows
|
|
56
|
+
* PowerShell 5.1), falling back to a bare `pwsh` resolved through PATH.
|
|
57
|
+
*/
|
|
58
|
+
pwshPath?: string;
|
|
59
|
+
}
|
|
60
|
+
/** The shape after schemastery applied the defaults (cwd/pwshPath have none). */
|
|
61
|
+
type ResolvedConfig = Required<Omit<Config, 'cwd' | 'pwshPath'>> & Pick<Config, 'cwd' | 'pwshPath'>;
|
|
62
|
+
export { candidatePwshPaths, resolvePwshPath } from './resolve.ts';
|
|
63
|
+
/**
|
|
64
|
+
* Local PowerShell executor over `ctx.subprocess`. Bounded output, spill
|
|
65
|
+
* files, and process-tree termination are the subprocess service's mechanics;
|
|
66
|
+
* this executor supplies their configured budgets per spawn.
|
|
67
|
+
*/
|
|
68
|
+
export declare class PwshLocalExecutor extends BashExecutor {
|
|
69
|
+
static inject: string[];
|
|
70
|
+
static Config: z<Config>;
|
|
71
|
+
/** Validated config (schemastery applied the defaults before construction). */
|
|
72
|
+
readonly config: ResolvedConfig;
|
|
73
|
+
/** The pwsh executable resolved once at construction. */
|
|
74
|
+
readonly pwshPath: string;
|
|
75
|
+
constructor(ctx: Context, config: Config);
|
|
76
|
+
/**
|
|
77
|
+
* Resolve a request into a fully-specified spec: fill `workdir` from
|
|
78
|
+
* `config.cwd` (else `process.cwd()`), and `timeoutMs` from
|
|
79
|
+
* `config.timeoutMs`, capped at `config.maxTimeoutMs`.
|
|
80
|
+
*/
|
|
81
|
+
resolve(request: BashExecRequest): BashExecSpec;
|
|
82
|
+
/**
|
|
83
|
+
* The pwsh invocation argv for one resolved spec — the argv-level seam a
|
|
84
|
+
* confining subclass wraps through `ctx.sandbox.confine` (the pwsh twin of
|
|
85
|
+
* `dsh-bash-local`'s `runArgv`/`startArgv` hooks; see
|
|
86
|
+
* `@deepseek-ai/dsh-pwsh-sandbox`).
|
|
87
|
+
*/
|
|
88
|
+
protected argv(spec: BashExecSpec): string[];
|
|
89
|
+
/** Map one resolved spec plus its argv onto a fully-specified subprocess spawn. */
|
|
90
|
+
private spawnSpec;
|
|
91
|
+
/** The collect-mode readers the executor itself requested (present by construction). */
|
|
92
|
+
private static collected;
|
|
93
|
+
run(spec: BashExecSpec): Promise<BashRunResult>;
|
|
94
|
+
/** Foreground run of an exact argv (the confining subclass re-wraps it). */
|
|
95
|
+
protected runArgv(spec: BashExecSpec, argv: readonly string[]): Promise<BashRunResult>;
|
|
96
|
+
start(spec: BashExecSpec): BashProcess;
|
|
97
|
+
/** Background start of an exact argv (the confining subclass re-wraps it). */
|
|
98
|
+
protected startArgv(spec: BashExecSpec, argv: readonly string[]): BashProcess;
|
|
99
|
+
/**
|
|
100
|
+
* Settlement hook for subclasses that attach execution facts to a process.
|
|
101
|
+
* The base implementation is intentionally empty. Mirrored from
|
|
102
|
+
* `dsh-bash-local` (whose sandboxing subclass consumes the same hook); the
|
|
103
|
+
* pwsh-confining consumer is `@deepseek-ai/dsh-pwsh-sandbox`.
|
|
104
|
+
* @param _proc - the settled process handle.
|
|
105
|
+
* @param _stderr - the process's retained stderr tail used by subclasses for settlement classification.
|
|
106
|
+
* @param _spawnFailed - whether the spawn rejected before any process existed.
|
|
107
|
+
* @param _spawnError - the spawn rejection, when `_spawnFailed`.
|
|
108
|
+
*/
|
|
109
|
+
protected onProcessDone(_proc: BashProcess, _stderr: string, _spawnFailed: boolean, _spawnError?: unknown): void;
|
|
110
|
+
}
|
|
111
|
+
export default PwshLocalExecutor;
|
|
112
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-owned invariant companion for `@deepseek-ai/dsh-pwsh-local`.
|
|
3
|
+
* @module @deepseek-ai/dsh-pwsh-local/invariant
|
|
4
|
+
*/
|
|
5
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
6
|
+
/** Cordis companion plugin name. */
|
|
7
|
+
export declare const name = "pwsh-local-invariant";
|
|
8
|
+
/** Service required before the companion can reserve package ownership. */
|
|
9
|
+
export declare const inject: string[];
|
|
10
|
+
/**
|
|
11
|
+
* Register this package's invariant companion.
|
|
12
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
13
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
14
|
+
*/
|
|
15
|
+
export declare const apply: (ctx: Context) => Promise<() => void>;
|
|
16
|
+
//# sourceMappingURL=invariant.d.ts.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PowerShell executable resolution, dependency-free so non-package consumers
|
|
3
|
+
* (the repository's coverage-gate probe in `vitest.config.ts`) can share the
|
|
4
|
+
* ONE resolution definition with the executor and its suites — a probe that
|
|
5
|
+
* resolved differently from the code under test could exempt a file whose
|
|
6
|
+
* suites actually run.
|
|
7
|
+
*
|
|
8
|
+
* @module @deepseek-ai/dsh-pwsh-local/resolve
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Well-known Windows PowerShell install locations plus PATH entries, newest
|
|
12
|
+
* first. Explicitly parameterized (env) so resolution is a pure function of
|
|
13
|
+
* its inputs on every platform.
|
|
14
|
+
* @param env - the environment to probe; defaults to the process environment.
|
|
15
|
+
* @returns candidate `pwsh` executable paths in resolution order.
|
|
16
|
+
*/
|
|
17
|
+
export declare function candidatePwshPaths(env?: NodeJS.ProcessEnv): string[];
|
|
18
|
+
/**
|
|
19
|
+
* Resolve the pwsh executable this executor spawns.
|
|
20
|
+
* @param configured - an explicit `pwshPath` config value, trusted as-is.
|
|
21
|
+
* @param env - the environment to probe on Windows; defaults to the process environment.
|
|
22
|
+
* @param platform - the platform to resolve for; defaults to the process platform.
|
|
23
|
+
* @returns the first existing well-known location on Windows (PowerShell 7
|
|
24
|
+
* install, a PATH entry such as the Microsoft Store install, then Windows
|
|
25
|
+
* PowerShell 5.1), else `pwsh` for PATH resolution.
|
|
26
|
+
*/
|
|
27
|
+
export declare function resolvePwshPath(configured?: string, env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): string;
|
|
28
|
+
//# sourceMappingURL=resolve.d.ts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@deepseek-ai/dsh-pwsh-local",
|
|
3
|
+
"description": "Local PowerShell implementation of the DeepSeek Harness bash executor seam",
|
|
4
|
+
"version": "0.0.1-rc.1",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "restricted"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
|
|
11
|
+
"directory": "packages/bash/pwsh-local"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "lib/index.js",
|
|
15
|
+
"types": "lib/types/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./lib/types/index.d.ts",
|
|
19
|
+
"default": "./lib/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./invariant": {
|
|
22
|
+
"types": "./lib/types/invariant.d.ts",
|
|
23
|
+
"default": "./lib/invariant.js"
|
|
24
|
+
},
|
|
25
|
+
"./src/*": "./src/*",
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"lib/index.js",
|
|
30
|
+
"lib/invariant.js",
|
|
31
|
+
"lib/types/**/*.d.ts"
|
|
32
|
+
],
|
|
33
|
+
"license": "BSD-3-Clause",
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@deepseek-ai/dsh-bash": "^0.0.1-rc.1",
|
|
36
|
+
"@deepseek-ai/dsh-invariants": "^0.0.1-rc.1",
|
|
37
|
+
"@deepseek-ai/dsh-subprocess": "^0.0.1-rc.1",
|
|
38
|
+
"@deepseek-ai/dsh-timeout": "^0.0.1-rc.1",
|
|
39
|
+
"@deepseek-ai/cordis": "^4.0.1-rc.1"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@deepseek-ai/schemastery": "^3.18.1-rc.1"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@deepseek-ai/dsh-bash": "^0.0.1-rc.1",
|
|
46
|
+
"@deepseek-ai/dsh-invariants": "^0.0.1-rc.1",
|
|
47
|
+
"@deepseek-ai/dsh-subprocess": "^0.0.1-rc.1",
|
|
48
|
+
"@deepseek-ai/dsh-subprocess-local": "^0.0.1-rc.1",
|
|
49
|
+
"@deepseek-ai/dsh-timeout": "^0.0.1-rc.1",
|
|
50
|
+
"@deepseek-ai/cordis": "^4.0.1-rc.1"
|
|
51
|
+
}
|
|
52
|
+
}
|