@deepseek-ai/dsh-shell 0.1.6-alpha.1 → 0.1.7-alpha.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 CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/shell/shell/README.md
5
- README.md: 9b32149691a8cf4d4d8316101f4feec6762d0846
6
- README.zh.md: 9d23b0c5957f9d59169c53c92b960c0b26b77a23
5
+ README.md: b72440a38cefb67140ba0078169aac34f3ccdb19
6
+ README.zh.md: 272ffc85f8c4880eb77348f2f5ce780a68529df6
package/README.md CHANGED
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
9
9
 
10
10
  ## Summary
11
11
 
12
- Use `ctx.shell` to run foreground shell commands with bounded output or prepare background processes asynchronously before receiving their handles. A profile can select local or sandboxed Bash or PowerShell execution without changing callers. Resolve each request before execution to make the working directory, timeout, and output limits explicit. Command completion, nonzero exits, timeouts, and caller aborts return results; only infrastructure failures reject, while the `bash` and `pwsh` tools own model-visible rendering and sandbox guidance.
12
+ Use `ctx.shell` to run shell commands with bounded output or keep them running as background work. One execution handle supports foreground results and background reads. A profile can select local or sandboxed Bash or PowerShell execution without changing callers. Resolve requests before execution to make the working directory, timeout, and output limits explicit. Command exits, timeouts, and caller aborts return results; only infrastructure failures reject, while the `bash` and `pwsh` tools own model-visible rendering and sandbox guidance.
13
13
 
14
14
  ## Table of Contents
15
15
 
@@ -25,24 +25,31 @@ Use `ctx.shell` to run foreground shell commands with bounded output or prepare
25
25
  <a id="use-this-package"></a>
26
26
  ## Use this package
27
27
 
28
- Use `ctx.shell` when an agent or an in-process plugin needs to run a shell command and read its output, or start a background process and poll it. It is the contract every shell executor and the model-facing `bash`/`pwsh` tools build on, so code written against it works over any executor implementation.
28
+ Use `ctx.shell` when an agent or an in-process plugin needs to run a shell command and read its output, keep a process running and poll it, or hand a foreground command a way to outlive its deadline. There is one execution method — `execute(spec)` resolves with the prepared handle — and "foreground" is a property of what the caller awaits, not of the spawn. It is the contract every shell executor and the model-facing `bash`/`pwsh` tools build on, so code written against it works over any executor implementation.
29
29
 
30
30
  ### Foreground commands
31
31
 
32
- Call `run` with a resolved spec to execute a command in the foreground. The promise resolves when the command finishes: a nonzero exit, an executor timeout kill, or a caller abort kill is a result, never a rejection. `run` rejects only for infrastructure failures such as an unusable working directory or a missing shell. The result carries the exit code or signal, whether a timeout or an abort cut the run short, and the collected stdout/stderr with spill-file paths when a stream overflowed its budget.
32
+ Await the handle's `result()` projection to run a command in the foreground. The promise resolves when the command finishes: a nonzero exit, an executor timeout kill, or a caller abort kill is a result, never a rejection. `result()` rejects only for infrastructure failures such as an unusable working directory or a missing shell. The result carries the exit code or signal, whether a timeout or an abort cut the run short (first-cause classification), and the collected stdout/stderr with spill-file paths when a stream overflowed its budget.
33
33
 
34
34
  ```text
35
- const result = await ctx.shell.run(ctx.shell.resolve({ command: 'ls -la' }))
35
+ const execution = await ctx.shell.execute(ctx.shell.resolve({ command: 'ls -la' }))
36
+ const result = await execution.result()
36
37
  console.log(result.exitCode, result.stdout.text)
37
38
  ```
38
39
 
39
40
  ### Background processes
40
41
 
41
- Await `start` with a resolved spec to launch a background process; it publishes the handle after preparation and applies no background execution timeout. Cancellation or preparation failure rejects before publication. Read output incrementally with `readOutput()` — consecutive reads never repeat output, and lossy reads point at full-stream spill files. Terminate the provider-managed range with `kill()` (returns `false` once the direct command has finished) and await `done` for direct-command settlement. Job ids, ownership, polling, and notices belong to the generic `ctx.jobs` runtime, where the tool layer registers the handle.
42
+ Resolve the request with `onExpiry: 'none'` and await `execute` for the prepared handle: no deadline is armed, and the process runs until killed or finished. Read output incrementally with `readOutput()` — consecutive reads never repeat output, and lossy reads point at full-stream spill files. Terminate the provider-managed range with `kill()` (returns `false` once the direct command has finished) and await `done` for direct-command settlement. Job ids, ownership, polling, and notices belong to the generic `ctx.jobs` runtime, where the tool layer registers the handle. `ShellProcess.observed` exposes non-consuming offset readers over the same captured streams — for observers independent of the consuming cursor, such as the job registry's pull sources — including the `spawn failed: …` note a rejected spawn leaves on stderr.
43
+
44
+ ### Deadlines and bounded waits
45
+
46
+ The deadline includes asynchronous preparation. Expiry before a process starts returns a settled handle whose result is `timedOut: true`, with empty output. Cancellation or preparation failure rejects `execute` before publication; late preparation cannot spawn a process.
47
+
48
+ The seam has two expiry policies and no hand-over protocol. `'kill'` stops the command at the deadline and classifies the result `timedOut`; `'none'` arms no deadline, so only the caller's signal and `kill()` stop the command. A caller that wants to wait only for a while runs the command under `'none'` and bounds its own wait: the handle stays valid after the caller stops waiting, and nothing changes hands at that moment. The `bash`/`pwsh` tools do exactly this — with a job registry composed they register every command with `ctx.jobs` as it starts and wait on the job, so a foreground command that outlives its timeout simply keeps running as the job it already was.
42
49
 
43
50
  ### Requests and resolved specs
44
51
 
45
- Every execution starts from a `ShellExecRequest` with optional fields; the executor's `resolve()` turns it into a fully-resolved `ShellExecSpec` with explicit defaults and caps before anything runs. This request/spec split is the repository's template for explicit resolution at package boundaries: callers never rely on hidden defaults inside `run` or `start`. `resolve()` fills the working directory and timeout from the executor's configuration, caps per-call overrides, and carries optional inputs — `stdin`, ordinary `env`, and the trusted `DSH_*` snapshot — through verbatim.
52
+ Every execution starts from a `ShellExecRequest` with optional fields; the executor's `resolve()` turns it into a fully-resolved `ShellExecSpec` with explicit defaults and caps before anything runs. This request/spec split is the repository's template for explicit resolution at package boundaries: callers never rely on hidden defaults inside `execute`. `resolve()` fills the working directory, timeout, and expiry policy (default `'kill'`) from the executor's configuration and the request, caps per-call overrides, and carries optional inputs — `stdin`, ordinary `env`, and the trusted `DSH_*` snapshot — through verbatim.
46
53
 
47
54
  ### Choosing and composing an executor
48
55
 
@@ -73,15 +80,15 @@ This section explains the design of the seam and points at the code that realize
73
80
 
74
81
  The package is one role of a standard capability seam: the Service Definition that names the executor contract, with Service Providers and Consumers split so each role evolves independently (see the [capability-seams note](../../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.md)). Two decisions anchor the contract:
75
82
 
76
- - **Explicit resolution at the boundary.** `resolve(request)` is the single place defaults and caps are applied; `run` and `start` accept only resolved specs and never re-default, so no hidden fallback lives inside an implementation.
77
- - **Task-free background handles.** `start` returns a `ShellProcess` with no id or owner; job identity, ownership, and lifecycle belong to the generic `ctx.jobs` runtime, keeping executors independent of sessions.
83
+ - **Explicit resolution at the boundary.** `resolve(request)` is the single place defaults and caps are applied; `execute` accepts only resolved specs and never re-defaults, so no hidden fallback lives inside an implementation.
84
+ - **One execution, projected views.** `execute` resolves with the prepared handle; the foreground result and the background cursor reads are projections over the same spawned process, so foreground/background is the caller's choice, never a second spawn path. The handle carries no id or owner; job identity, ownership, and lifecycle belong to the generic `ctx.jobs` runtime, keeping executors independent of sessions.
78
85
 
79
86
  ### Source map
80
87
 
81
88
  | File | Role |
82
89
  |---|---|
83
90
  | [`src/index.ts`](src/index.ts) | Plugin entry: abstract `ShellExecutor` service and the shared settings namespace |
84
- | [`src/types.ts`](src/types.ts) | Request/spec vocabulary, `ShellRunResult`, `ShellProcess`, and sandbox facts |
91
+ | [`src/types.ts`](src/types.ts) | Request/spec vocabulary, `ShellExecution`, `ShellRunResult`, and sandbox facts |
85
92
  | [`src/render.ts`](src/render.ts) | `parseExitStatus`: the exit-status marker contract the shell tools share |
86
93
  | — | No runtime invariant companion is published; this stateless Service Definition owns request/result types, while executors and policy own observations. |
87
94
 
@@ -91,7 +98,7 @@ The package is one role of a standard capability seam: the Service Definition th
91
98
 
92
99
  ### Background lifecycle and ownership
93
100
 
94
- A background process belongs to the subprocess service, not to the executor: it survives an executor-only reload and is killed and joined when the composition tears down. Implementations must honor the seam's semantics — `run` rejects only for infrastructure failures; `start` resolves after preparation with no background execution timeout and its published handle’s `done` never rejects (a subprocess provider rejection settles as `killed` with a stage-neutral error on stderr); `readOutput` is consuming and lossy reads report spill files.
101
+ A spawned process belongs to the subprocess service, not to the executor: it survives an executor-only reload and is killed and joined when the composition tears down. Implementations must honor the seam's semantics — `result()` rejects only for infrastructure failures; the handle is published after preparation and its `done` never rejects (provider rejections, synchronous or asynchronous, settle the handle as `killed` with a stage-neutral note on stderr while `result()` carries the same failure as its rejection; a live handle whose rejection follows the execution's own `kill()` or abort settles as its terminal outcome instead); `readOutput` is consuming and lossy reads report spill files.
95
102
 
96
103
  </details>
97
104
 
package/README.zh.md CHANGED
@@ -9,7 +9,7 @@ kind: "package-reference"
9
9
 
10
10
  ## 概述
11
11
 
12
- 使用 `ctx.shell` 运行输出有界的前台 shell 命令,或异步准备后台进程后取得句柄。配置文件可选择本地或沙箱化的 Bash 或 PowerShell 执行方式,而无需更改调用方。执行前解析每个请求,以显式确定工作目录、超时和输出上限。命令完成、非零退出、超时和调用方中止都会作为结果返回;只有基础设施故障才会 reject,而模型可见的渲染与沙箱指引由 `bash` 和 `pwsh` 工具负责。
12
+ 使用 `ctx.shell` 运行输出有界的 shell 命令,或让命令作为后台工作继续运行。同一个执行句柄支持前台结果与后台读取。配置文件可以选择本地或沙箱化的 Bash 或 PowerShell 执行方式,而无需更改调用方。执行前解析请求,以显式确定工作目录、超时与输出上限。命令退出、超时和调用方中止都会作为结果返回;只有基础设施故障才会 reject,模型可见的渲染与沙箱指引由 `bash` 和 `pwsh` 工具负责。
13
13
 
14
14
  ## 目录
15
15
 
@@ -25,24 +25,31 @@ kind: "package-reference"
25
25
  <a id="use-this-package"></a>
26
26
  ## 使用本包
27
27
 
28
- 当 agent(智能体)或进程内插件需要运行 shell 命令并读取输出,或启动后台进程并轮询它时,使用 `ctx.shell`。它是每个 shell 执行器与面向模型的 `bash`/`pwsh` 工具共同依赖的约定,因此基于它编写的代码可以运行在任意执行器实现之上。
28
+ 当 agent(智能体)或进程内插件需要运行 shell 命令并读取输出、保持进程运行并轮询它,或让前台命令有办法活过自己的 deadline 时,使用 `ctx.shell`。执行方法只有一个——`execute(spec)` 在准备完成后返回句柄——「前台」是调用方等待什么的属性,而不是 spawn 的属性。它是每个 shell 执行器与面向模型的 `bash`/`pwsh` 工具共同依赖的约定,因此基于它编写的代码可以运行在任意执行器实现之上。
29
29
 
30
30
  ### 前台命令
31
31
 
32
- 用已解析的 spec 调用 `run` 即可在前台执行命令。promise 在命令结束时 resolve:非零退出、执行器超时终止或调用方中止终止都是结果,绝不是 rejection。`run` 只在基础设施失败时 reject,例如工作目录不可用或缺少 shell。结果携带退出码或信号、是超时还是中止截断了运行,以及收集到的 stdout/stderr;流超出预算时还附带 spill 文件路径。
32
+ 等待句柄的 `result()` 投影即可在前台执行命令。promise 在命令结束时 resolve:非零退出、执行器超时终止或调用方中止终止都是结果,绝不是 rejection。`result()` 只在基础设施失败时 reject,例如工作目录不可用或缺少 shell。结果携带退出码或信号、是超时还是中止截断了运行(首因归类),以及收集到的 stdout/stderr;流超出预算时还附带 spill 文件路径。
33
33
 
34
34
  ```text
35
- const result = await ctx.shell.run(ctx.shell.resolve({ command: 'ls -la' }))
35
+ const execution = await ctx.shell.execute(ctx.shell.resolve({ command: 'ls -la' }))
36
+ const result = await execution.result()
36
37
  console.log(result.exitCode, result.stdout.text)
37
38
  ```
38
39
 
39
40
  ### 后台进程
40
41
 
41
- 用已解析的 spec 等待 `start` 即可启动后台进程;它在准备完成后发布句柄,不应用后台执行超时。取消或准备失败会在发布前拒绝调用。用 `readOutput()` 增量读取输出——连续读取绝不会重复交付,有损读取会指向完整流的 spill 文件。用 `kill()` 终止由提供方管理的进程范围(直接命令结束后返回 `false`),并等待 `done` 完成直接命令结算。job id、所有权、轮询与通知属于通用 `ctx.jobs` 运行时,工具层会把句柄注册进去。
42
+ 以 `onExpiry: 'none'` 解析请求并保留句柄:不布置任何 deadline,进程一直跑到被 kill 或自行结束。用 `readOutput()` 增量读取输出——连续读取绝不会重复交付,有损读取会指向完整流的 spill 文件。用 `kill()` 终止由提供方管理的进程范围(直接命令结束后返回 `false`),并等待 `done` 完成直接命令结算。job id、所有权、轮询与通知属于通用 `ctx.jobs` 运行时,工具层会把句柄注册进去。`ShellProcess.observed` 在同一份捕获流上暴露非消费的偏移读取器——供独立于消费游标的观察者使用,例如任务注册表的拉取源——包括被拒绝的 spawn 留在 stderr 上的 `spawn failed: …` 提示。
43
+
44
+ ### deadline 与有界等待
45
+
46
+ deadline 包含异步准备耗时。进程启动前到期会返回已结算的句柄,其结果为 `timedOut: true`,输出为空。取消或准备失败会在发布句柄前拒绝 `execute`;迟到的准备结果不会启动进程。
47
+
48
+ seam 只有两种到期策略,没有任何移交协议。`'kill'` 在 deadline 停下命令并把结果归类为 `timedOut`;`'none'` 不设 deadline,只有调用方的信号和 `kill()` 能停下命令。只想等一段时间的调用方以 `'none'` 运行命令并自己限定等待时长:调用方停止等待后句柄依然有效,那一刻没有任何东西易手。`bash`/`pwsh` 工具正是这样做的——组合中有 job registry 时,每条命令一启动就登记到 `ctx.jobs` 并等待该 job,所以超时后仍在运行的前台命令只是继续作为它本来就是的那个 job 运行。
42
49
 
43
50
  ### 请求与已解析 spec
44
51
 
45
- 每次执行都从带可选字段的 `ShellExecRequest` 开始;执行器的 `resolve()` 在任何东西运行之前,把它变成默认值与上限都已显式填好的 `ShellExecSpec`。这一请求/spec 拆分正是仓库在包边界显式解析的模板:调用方绝不依赖 `run` 或 `start` 内部隐藏的默认值。`resolve()` 从执行器配置填充工作目录与超时、对每次调用的覆盖值设上限,并按原样携带可选输入——`stdin`、普通 `env` 与受信任的 `DSH_*` 快照。
52
+ 每次执行都从带可选字段的 `ShellExecRequest` 开始;执行器的 `resolve()` 在任何东西运行之前,把它变成默认值与上限都已显式填好的 `ShellExecSpec`。这一请求/spec 拆分正是仓库在包边界显式解析的模板:调用方绝不依赖 `execute` 内部隐藏的默认值。`resolve()` 从执行器配置与请求填充工作目录、超时与到期策略(默认 `'kill'`)、对每次调用的覆盖值设上限,并按原样携带可选输入——`stdin`、普通 `env` 与受信任的 `DSH_*` 快照。
46
53
 
47
54
  ### 选择并组合一个执行器
48
55
 
@@ -73,15 +80,15 @@ seam 本身不是执行器:每个组合只挂载一个提供方,工具即可
73
80
 
74
81
  本包是标准能力 seam 中的一个角色:命名执行器约定的 Service Definition,Service Provider 与 Consumer 各自拆分,使每个角色都能独立演进(见[能力 seam 笔记](../../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.zh.md))。两项决策锚定了该约定:
75
82
 
76
- - **边界处的显式解析。** `resolve(request)` 是应用默认值与上限的唯一位置;`run` 与 `start` 只接受已解析的 spec,绝不再次默认化,因此实现内部不会藏有隐藏的兜底值。
77
- - **无任务语义的后台句柄。** `start` 返回不带 id 或所有者的 `ShellProcess`;job 身份、所有权与生命周期属于通用 `ctx.jobs` 运行时,使执行器与会话保持独立。
83
+ - **边界处的显式解析。** `resolve(request)` 是应用默认值与上限的唯一位置;`execute` 只接受已解析的 spec,绝不再次默认化,因此实现内部不会藏有隐藏的兜底值。
84
+ - **一次执行,多个投影。** `execute` 在准备完成后返回句柄;前台结果与后台游标读取都是同一个已 spawn 进程上的投影,前台/后台是调用方的选择,绝不是第二条 spawn 路径。句柄不带 id 或所有者;job 身份、所有权与生命周期属于通用 `ctx.jobs` 运行时,使执行器与会话保持独立。
78
85
 
79
86
  ### 源码地图
80
87
 
81
88
  | 文件 | 职责 |
82
89
  |---|---|
83
90
  | [`src/index.ts`](src/index.ts) | 插件入口:抽象 `ShellExecutor` 服务与共享设置命名空间 |
84
- | [`src/types.ts`](src/types.ts) | 请求/spec 词汇、`ShellRunResult`、`ShellProcess` 与沙箱事实 |
91
+ | [`src/types.ts`](src/types.ts) | 请求/spec 词汇、`ShellExecution`、`ShellRunResult` 与沙箱事实 |
85
92
  | [`src/render.ts`](src/render.ts) | `parseExitStatus`:shell 工具共享的退出状态标记约定 |
86
93
  | — | 不发布运行时不变式伴生入口;该无状态 Service Definition 负责请求/结果类型,执行器与策略负责观察。 |
87
94
 
@@ -91,7 +98,7 @@ seam 本身不是执行器:每个组合只挂载一个提供方,工具即可
91
98
 
92
99
  ### 后台生命周期与归属
93
100
 
94
- 后台进程属于 subprocess 服务而非执行器:它能在仅重载执行器后存活,并在组合拆解时被终止并 join。实现必须遵守 seam 的语义——`run` 只在基础设施失败时 reject;`start` 在准备完成后返回且不设后台执行超时,已发布句柄的 `done` 绝不 reject(subprocess provider rejection 以 `killed` 结算,并把不声明阶段的错误写入 stderr);`readOutput` 是消费式的,有损读取会报告 spill 文件。
101
+ 已 spawn 的进程属于 subprocess 服务而非执行器:它能在仅重载执行器后存活,并在组合拆解时被终止并 join。实现必须遵守 seam 的语义——`result()` 只在基础设施失败时 reject;句柄在准备完成后发布且其 `done` 绝不 reject(无论同步还是异步的 provider rejection 都把句柄结算为 `killed`、把不声明阶段的提示写入 stderr,同时 `result()` 以同一失败 reject;活句柄在本次执行自己的 `kill()` 或 abort 之后才到来的 rejection 则结算为其终态);`readOutput` 是消费式的,有损读取会报告 spill 文件。
95
102
 
96
103
  </details>
97
104
 
package/lib/index.js CHANGED
@@ -53,34 +53,32 @@ function parseExitStatus(text) {
53
53
  * @module @deepseek-ai/dsh-shell
54
54
  */
55
55
  /**
56
- * Settings namespace of this capability, owned here rather than by either
57
- * executor family because it names the capability, not an implementation: a
58
- * host composes exactly one provider of `ctx.shell` (the win32 layer swaps the
59
- * POSIX rows for the pwsh ones, and mounting both fails loud on a duplicate
60
- * service registration), so the providers share one namespace without ever
61
- * registering it twice, and a settings document carried between platforms
62
- * keeps resolving on both.
63
- */
64
- const SHELL_SETTINGS_NAMESPACE = "shell";
65
- /**
66
56
  * Abstract bash execution service. Subclass, implement the abstract methods,
67
57
  * and load the subclass as a plugin — it registers as `ctx.shell` (one
68
58
  * implementation per context; loading a second throws, which is cordis'
69
59
  * standard duplicate-service behavior).
70
60
  *
61
+ * {@link execute} resolves with the process handle after preparation. "Foreground" is a property of what the caller awaits, not
62
+ * of the spawn — a caller that awaits {@link ShellExecution.result} ran the
63
+ * command in the foreground; one that keeps the handle ran it in the
64
+ * background. A caller that waits only for a while runs the command under
65
+ * `onExpiry: 'none'` and bounds its own wait; the handle stays valid after
66
+ * the caller stops waiting.
67
+ *
71
68
  * Implementations must honor these semantics:
72
- * - {@link run} rejects only for infrastructure failures. Nonzero exits,
73
- * timeout kills, and abort kills resolve with a {@link ShellRunResult}.
74
- * - {@link start} resolves after launch preparation; cancellation or setup failure
75
- * rejects before publishing a handle. No timeout applies to background processes.
76
- * Once published, `done` settles at process close and never rejects; subprocess
77
- * provider failures settle as `killed` with the error on stderr.
69
+ * - {@link ShellExecution.result} rejects only for infrastructure failures.
70
+ * Nonzero exits, timeout kills, and abort kills resolve with a descriptive
71
+ * result: first-cause `timedOut`/`aborted`, the spec's `timeoutMs` echoed.
72
+ * - The handle is published after preparation. `done` settles at process close
73
+ * and never rejects; spawn failures settle as `killed` with the error on the read
74
+ * path, while `result()` carries the same failure as its rejection.
75
+ * - `onExpiry: 'none'` arms no deadline; `'kill'` kills at expiry. Expiry
76
+ * during preparation returns a settled timed-out handle without output.
78
77
  * - {@link ShellProcess.readOutput} is incremental: consecutive reads never
79
78
  * repeat output. Lossy reads report truncation and available spill files.
80
- * - A still-running background process is stopped and awaited when its
81
- * owning composition tears down. With the subprocess seam that
82
- * boundary is `ctx.subprocess` disposal, so a background process survives
83
- * an executor-only reload.
79
+ * - A still-running process is stopped and awaited when its owning
80
+ * composition tears down. With the subprocess seam that boundary is
81
+ * `ctx.subprocess` disposal, so a process survives an executor-only reload.
84
82
  */
85
83
  var ShellExecutor = class extends Service {
86
84
  constructor(ctx) {
@@ -94,4 +92,4 @@ var ShellExecutor = class extends Service {
94
92
  get sandboxMode() {}
95
93
  };
96
94
  //#endregion
97
- export { DSH_ENV_PREFIX, SHELL_SETTINGS_NAMESPACE, ShellExecutor, ShellExecutor as default, parseExitStatus };
95
+ export { DSH_ENV_PREFIX, ShellExecutor, ShellExecutor as default, parseExitStatus };
@@ -6,19 +6,9 @@
6
6
  */
7
7
  import { Context, Service } from '@deepseek-ai/cordis';
8
8
  import type { SandboxMode } from '@deepseek-ai/dsh-sandbox';
9
- import type { ShellExecRequest, ShellExecSpec, ShellProcess, ShellRunResult } from './types.ts';
10
- /**
11
- * Settings namespace of this capability, owned here rather than by either
12
- * executor family because it names the capability, not an implementation: a
13
- * host composes exactly one provider of `ctx.shell` (the win32 layer swaps the
14
- * POSIX rows for the pwsh ones, and mounting both fails loud on a duplicate
15
- * service registration), so the providers share one namespace without ever
16
- * registering it twice, and a settings document carried between platforms
17
- * keeps resolving on both.
18
- */
19
- export declare const SHELL_SETTINGS_NAMESPACE = "shell";
9
+ import type { ShellExecRequest, ShellExecSpec, ShellExecution } from './types.ts';
20
10
  export { DSH_ENV_PREFIX } from './types.ts';
21
- export type { ShellExecRequest, ShellExecSpec, ShellProcess, ShellProcessRead, ShellProcessStatus, ShellRunResult, ShellSandboxInfo, CollectedOutput, DshEnvironment, DshEnvironmentKey, } from './types.ts';
11
+ export type { ShellExecRequest, ShellExecSpec, ShellExecution, ShellExpiryPolicy, ShellProcess, ShellProcessRead, ShellProcessStatus, ShellRunResult, ShellSandboxInfo, CollectedOutput, DshEnvironment, DshEnvironmentKey, } from './types.ts';
22
12
  export { parseExitStatus } from './render.ts';
23
13
  export type { ParsedExitStatus } from './render.ts';
24
14
  declare module '@deepseek-ai/cordis' {
@@ -32,19 +22,27 @@ declare module '@deepseek-ai/cordis' {
32
22
  * implementation per context; loading a second throws, which is cordis'
33
23
  * standard duplicate-service behavior).
34
24
  *
25
+ * {@link execute} resolves with the process handle after preparation. "Foreground" is a property of what the caller awaits, not
26
+ * of the spawn — a caller that awaits {@link ShellExecution.result} ran the
27
+ * command in the foreground; one that keeps the handle ran it in the
28
+ * background. A caller that waits only for a while runs the command under
29
+ * `onExpiry: 'none'` and bounds its own wait; the handle stays valid after
30
+ * the caller stops waiting.
31
+ *
35
32
  * Implementations must honor these semantics:
36
- * - {@link run} rejects only for infrastructure failures. Nonzero exits,
37
- * timeout kills, and abort kills resolve with a {@link ShellRunResult}.
38
- * - {@link start} resolves after launch preparation; cancellation or setup failure
39
- * rejects before publishing a handle. No timeout applies to background processes.
40
- * Once published, `done` settles at process close and never rejects; subprocess
41
- * provider failures settle as `killed` with the error on stderr.
33
+ * - {@link ShellExecution.result} rejects only for infrastructure failures.
34
+ * Nonzero exits, timeout kills, and abort kills resolve with a descriptive
35
+ * result: first-cause `timedOut`/`aborted`, the spec's `timeoutMs` echoed.
36
+ * - The handle is published after preparation. `done` settles at process close
37
+ * and never rejects; spawn failures settle as `killed` with the error on the read
38
+ * path, while `result()` carries the same failure as its rejection.
39
+ * - `onExpiry: 'none'` arms no deadline; `'kill'` kills at expiry. Expiry
40
+ * during preparation returns a settled timed-out handle without output.
42
41
  * - {@link ShellProcess.readOutput} is incremental: consecutive reads never
43
42
  * repeat output. Lossy reads report truncation and available spill files.
44
- * - A still-running background process is stopped and awaited when its
45
- * owning composition tears down. With the subprocess seam that
46
- * boundary is `ctx.subprocess` disposal, so a background process survives
47
- * an executor-only reload.
43
+ * - A still-running process is stopped and awaited when its owning
44
+ * composition tears down. With the subprocess seam that boundary is
45
+ * `ctx.subprocess` disposal, so a process survives an executor-only reload.
48
46
  */
49
47
  export declare abstract class ShellExecutor extends Service {
50
48
  constructor(ctx: Context);
@@ -58,23 +56,17 @@ export declare abstract class ShellExecutor extends Service {
58
56
  * Apply implementation-owned defaults and caps to a request before execution.
59
57
  * @param request - the caller's request; omitted fields get this
60
58
  * implementation's defaults, capped fields are clamped.
61
- * @returns the fully-specified spec to hand to {@link run}/{@link start}.
59
+ * @returns the fully-specified spec to hand to {@link execute}.
62
60
  */
63
61
  abstract resolve(request: ShellExecRequest): ShellExecSpec;
64
62
  /**
65
- * Run preparation and the foreground command under the resolved timeout.
63
+ * Prepare and spawn the command under its resolved deadline.
66
64
  * @param spec - a resolved spec from {@link resolve}, never a raw request.
67
- * @returns the outcome; nonzero exits, timeout kills, and abort kills
68
- * resolve with a descriptive result rather than reject.
65
+ * @returns the prepared handle, including its result projection;
66
+ * preparation timeout yields an already-settled handle with no output.
69
67
  * @throws on preparation failure or caller cancellation before process publication.
70
68
  */
71
- abstract run(spec: ShellExecSpec): Promise<ShellRunResult>;
72
- /**
73
- * Prepare a background process asynchronously and publish its live handle.
74
- * @param spec - a resolved spec from {@link resolve}, never a raw request.
75
- * @returns the live process handle after preparation; cancellation or setup failure rejects.
76
- */
77
- abstract start(spec: ShellExecSpec): Promise<ShellProcess>;
69
+ abstract execute(spec: ShellExecSpec): Promise<ShellExecution>;
78
70
  }
79
71
  export default ShellExecutor;
80
72
  //# sourceMappingURL=index.d.ts.map
@@ -7,9 +7,22 @@
7
7
  * @module dsh-shell/types
8
8
  */
9
9
  import type { SandboxEnforcement, SandboxExecutionPolicy, SandboxMode } from '@deepseek-ai/dsh-sandbox';
10
- import type { CollectedOutput, DshEnvironment } from '@deepseek-ai/dsh-subprocess';
10
+ import type { CollectedOutput, DshEnvironment, SubprocessOutputReader } from '@deepseek-ai/dsh-subprocess';
11
11
  export { DSH_ENV_PREFIX } from '@deepseek-ai/dsh-subprocess';
12
- export type { CollectedOutput, DshEnvironment, DshEnvironmentKey } from '@deepseek-ai/dsh-subprocess';
12
+ export type { CollectedOutput, DshEnvironment, DshEnvironmentKey, SubprocessOutputRead, SubprocessOutputReader } from '@deepseek-ai/dsh-subprocess';
13
+ /**
14
+ * Non-consuming offset readers over a background process's captured streams,
15
+ * for observers independent of the consuming {@link ShellProcess.readOutput}
16
+ * cursor. Every background process exposes both streams; a spawn that
17
+ * rejected produced no process output, so its stderr reader serves the
18
+ * `spawn failed: …` note as the whole stream.
19
+ */
20
+ export interface ShellObservedStreams {
21
+ /** Offset reader over captured stdout. */
22
+ stdout: SubprocessOutputReader;
23
+ /** Offset reader over captured stderr (the spawn-failure note after a rejected spawn). */
24
+ stderr: SubprocessOutputReader;
25
+ }
13
26
  /**
14
27
  * Sandbox facts for one run, present iff a sandboxing executor handled it.
15
28
  * Facts are reported independently of process exit status so callers can
@@ -25,6 +38,14 @@ export interface ShellSandboxInfo {
25
38
  /** Whether the sandbox runner failed before the command could run. */
26
39
  runnerFailed?: boolean;
27
40
  }
41
+ /**
42
+ * What the executor does when the deadline expires: `kill` stops the command
43
+ * and classifies the result `timedOut` (the default), and `none` arms no
44
+ * deadline at all, leaving the caller's signal and {@link ShellProcess.kill}
45
+ * as the only ways to stop the command. A consumer that wants to keep waiting
46
+ * only for a while runs the command under `none` and bounds its own wait.
47
+ */
48
+ export type ShellExpiryPolicy = 'kill' | 'none';
28
49
  /**
29
50
  * A caller's execution REQUEST: `workdir` and `timeoutMs` are optional and
30
51
  * filled by {@link ShellExecutor.resolve} from the implementation's config.
@@ -37,6 +58,8 @@ export interface ShellExecRequest {
37
58
  workdir?: string | undefined;
38
59
  /** Timeout override in milliseconds (implementations cap it). */
39
60
  timeoutMs?: number | undefined;
61
+ /** Deadline policy at `timeoutMs` expiry (default `'kill'`). */
62
+ onExpiry?: ShellExpiryPolicy | undefined;
40
63
  /**
41
64
  * Foreground stdout capture budget in bytes. Absent uses the executor's
42
65
  * default output cap. Trusted in-process consumers use this when they must
@@ -44,7 +67,7 @@ export interface ShellExecRequest {
44
67
  * tool does not expose it as a parameter.
45
68
  */
46
69
  stdoutMaxBytes?: number | undefined;
47
- /** Abort signal — implementations kill the command when it fires. */
70
+ /** Abort signal — implementations kill the command when it fires, and treat a signal that is already aborted as fired. */
48
71
  signal?: AbortSignal | undefined;
49
72
  /**
50
73
  * Bytes to write to the command's stdin, then close it. Absent leaves stdin
@@ -75,19 +98,21 @@ export interface ShellExecRequest {
75
98
  }
76
99
  /**
77
100
  * A resolved execution spec. {@link ShellExecutor.resolve} fills and caps the
78
- * required fields; {@link ShellExecutor.start} ignores `timeoutMs` because
79
- * background processes have no executor timeout.
101
+ * required fields; under `onExpiry: 'none'` the resolved `timeoutMs` arms no
102
+ * timer and is only echoed into {@link ShellRunResult.timeoutMs}.
80
103
  */
81
104
  export interface ShellExecSpec {
82
105
  command: string;
83
106
  workdir: string;
84
107
  timeoutMs: number;
108
+ /** Deadline policy at `timeoutMs` expiry ({@link ShellExecutor.resolve} defaults it to `'kill'`). */
109
+ onExpiry: ShellExpiryPolicy;
85
110
  /**
86
- * Resolved foreground stdout capture budget in bytes. `run()` uses it for
87
- * stdout; background jobs and stderr keep the executor's own output cap.
111
+ * Resolved stdout capture budget in bytes, applied to every execution's
112
+ * stdout; stderr keeps the executor's own output cap.
88
113
  */
89
114
  stdoutMaxBytes: number;
90
- /** Abort signal — implementations kill the command when it fires. */
115
+ /** Abort signal — implementations kill the command when it fires, and treat a signal that is already aborted as fired. */
91
116
  signal?: AbortSignal | undefined;
92
117
  /** Bytes to write to stdin before closing it; absent means no stdin. */
93
118
  stdin?: string | undefined;
@@ -169,10 +194,34 @@ export interface ShellProcess {
169
194
  * full-stream spill files when available.
170
195
  */
171
196
  readOutput(): ShellProcessRead;
197
+ /**
198
+ * Non-consuming offset readers over the same captured streams the consuming
199
+ * {@link readOutput} cursor drains, including the provider-failure note a
200
+ * rejected spawn leaves on stderr. Independent observers read here at their
201
+ * own offsets without stealing bytes from `readOutput`.
202
+ */
203
+ observed: ShellObservedStreams;
172
204
  /**
173
205
  * Terminate the provider-managed range. Returns false when it had already finished
174
206
  * (no-op); idempotent.
175
207
  */
176
208
  kill(): boolean;
177
209
  }
210
+ /**
211
+ * The one execution handle {@link ShellExecutor.execute} returns: the live
212
+ * {@link ShellProcess} itself plus the foreground projection `result()`.
213
+ */
214
+ export interface ShellExecution extends ShellProcess {
215
+ /**
216
+ * Foreground projection: settles when the process closes, with split
217
+ * collected streams and first-cause `timedOut`/`aborted` classification.
218
+ * Rejects only for infrastructure failures (a spawn that never produced a
219
+ * process); nonzero exits, timeout kills, and abort kills resolve with a
220
+ * descriptive result. Created on demand and memoized — callers that never
221
+ * invoke it (background producers) never observe the rejection either; the
222
+ * handle's `done`/read path carries the spawn-failure story for them.
223
+ * @returns the settled foreground result for this execution.
224
+ */
225
+ result(): Promise<ShellRunResult>;
226
+ }
178
227
  //# sourceMappingURL=types.d.ts.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-shell",
3
3
  "description": "Abstract bash executor seam (ctx.shell) for the DeepSeek Harness",
4
- "version": "0.1.6-alpha.1",
4
+ "version": "0.1.7-alpha.1",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -27,15 +27,13 @@
27
27
  ],
28
28
  "license": "MIT",
29
29
  "peerDependencies": {
30
- "@deepseek-ai/dsh-subprocess": "^0.1.6-alpha.1",
31
- "@deepseek-ai/dsh-sandbox": "^0.1.6-alpha.1",
32
- "@deepseek-ai/cordis": "^4.0.2",
33
- "@deepseek-ai/dsh-settings": "^0.1.6-alpha.1"
30
+ "@deepseek-ai/dsh-subprocess": "^0.1.7-alpha.1",
31
+ "@deepseek-ai/dsh-sandbox": "^0.1.7-alpha.1",
32
+ "@deepseek-ai/cordis": "^4.0.3"
34
33
  },
35
34
  "devDependencies": {
36
- "@deepseek-ai/dsh-subprocess": "^0.1.6-alpha.1",
37
- "@deepseek-ai/cordis": "^4.0.2",
38
- "@deepseek-ai/dsh-sandbox": "^0.1.6-alpha.1",
39
- "@deepseek-ai/dsh-settings": "^0.1.6-alpha.1"
35
+ "@deepseek-ai/dsh-subprocess": "^0.1.7-alpha.1",
36
+ "@deepseek-ai/dsh-sandbox": "^0.1.7-alpha.1",
37
+ "@deepseek-ai/cordis": "^4.0.3"
40
38
  }
41
39
  }