@deepseek-ai/dsh-tool-bash 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 +2 -2
- package/README.md +16 -10
- package/README.zh.md +16 -10
- package/lib/index.js +421 -183
- package/lib/types/background.d.ts +33 -10
- package/lib/types/index.d.ts +20 -4
- package/lib/types/render.d.ts +28 -9
- package/package.json +33 -33
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/tool-bash/README.md
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: 3068e6d201758a2f66529155de12d1dbf912bae2
|
|
6
|
+
README.zh.md: 032d59efa33b702c8d0b202ebd33e60ae63f6804
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
|
|
|
9
9
|
|
|
10
10
|
## Summary
|
|
11
11
|
|
|
12
|
-
`dsh-tool-bash`
|
|
12
|
+
`dsh-tool-bash` runs Bash commands and returns stdout, stderr, and exit markers. Each call uses a fresh shell; cwd, variables, and functions do not persist. With a job registry composed, every command is a job from its start: `run_in_background` returns the id at once, a foreground command that outlives its timeout returns the same id, and `job_output`/`job_kill` collect and stop it. Commands receive the managed `DSH_*` environment; sandbox denials can be retried once with wider `sandbox_permissions`, a `justification`, and user approval. Nonzero exits are results for the agent to interpret. Mount an executor such as `dsh-bash-local` or `dsh-bash-sandbox` with `dsh-shell-env`.
|
|
13
13
|
|
|
14
14
|
## Table of Contents
|
|
15
15
|
|
|
@@ -41,11 +41,12 @@ The common path is an executor provider, the environment registry, and this tool
|
|
|
41
41
|
- name: '@deepseek-ai/dsh-tool-jobs'
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
The
|
|
44
|
+
The config fields govern the background surface.
|
|
45
45
|
|
|
46
46
|
| Field | Default | Meaning |
|
|
47
47
|
|---|---|---|
|
|
48
|
-
| `enableRunInBackground` | `true` | Expose `run_in_background
|
|
48
|
+
| `enableRunInBackground` | `true` | Expose `run_in_background` while a job registry is composed; when `false`, forced background calls are rejected |
|
|
49
|
+
| `promoteOnTimeout` | `true` | Keep a foreground command that reaches its timeout running as its background job instead of killing it |
|
|
49
50
|
|
|
50
51
|
The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-tool-bash) is the exhaustive source for every accepted field and its JSDoc; the generated [tool catalog](../../../docs/tool-catalog.md#deepseek-aidsh-tool-bash) carries the full argument schema.
|
|
51
52
|
|
|
@@ -56,11 +57,15 @@ The tool executes `bash -c <command>` and returns the combined output. Commands
|
|
|
56
57
|
<a id="running-long-commands-in-the-background"></a>
|
|
57
58
|
### Running long commands in the background
|
|
58
59
|
|
|
59
|
-
Passing `run_in_background: true` admits a job and returns its id immediately; confinement preparation may still be pending, and no background execution timeout applies. Output is empty until the process is available. Job cancellation aborts preparation and stops any process that arrives afterward; startup failure settles the admitted job as failed. The agent reads its output with `job_output` (non-blocking unless `wait: true`), lists jobs with `job_list`, and stops it with `job_kill`; a finished job notifies the owning agent in-session. Background support needs the generic job runtime (`dsh-jobs-local`) and its control tools (`dsh-tool-jobs`) mounted.
|
|
60
|
+
Passing `run_in_background: true` admits a job and returns its id immediately; confinement preparation may still be pending, and no background execution timeout applies. Output is empty until the process is available. Job cancellation aborts preparation and stops any process that arrives afterward; startup failure settles the admitted job as failed. The agent reads its output with `job_output` (non-blocking unless `wait: true`), lists jobs with `job_list`, and stops it with `job_kill`; a finished job notifies the owning agent in-session. Background support needs the generic job runtime (`dsh-jobs-local`) and its control tools (`dsh-tool-jobs`) mounted. The background job hands the run's non-consuming `observed` readers to the job registry as pull sources; the registry pumps them into the job's output ring at its own cadence (`pumpPollMs` on `dsh-jobs-local`), so the Web client streams live output and the model's `job_output` reads consume the same bytes through a separate cursor. A reader that throws is logged once and its stream stops; the job runs on to its own settlement. The ring is a best-effort live preview: stdout and stderr are copied per poll round, so writes the two streams made inside one poll window appear stdout first rather than in write order.
|
|
61
|
+
|
|
62
|
+
### Foreground commands as jobs
|
|
63
|
+
|
|
64
|
+
With a job registry composed, a foreground command is registered with `ctx.jobs` at its start and the call waits on that job: the command is listed, streams through `job.list` and `job.follow`, and can be stopped from the Web task list for as long as it runs. A command that finishes within the timeout returns the ordinary foreground result and its job record leaves the registry with it, so the model never sees an id. A command that outlives the timeout keeps running as the job it already was, and the call returns `[still running after <timeoutMs>ms; moved to background job <id>]` plus the job hand-off guidance, seeded with one consuming read of the output so far — `job_output` continues exactly after it. A kill from outside the call (the human stopping the job) settles the foreground result with `[stopped: <reason>]` ahead of the signal marker, so the model reads the reason instead of a command failure; cancelling the call itself kills the job. Registration is best-effort: `promoteOnTimeout: false`, a missing job registry, or a registry that refuses the job at its start (the owner's job limit, no controller) run the command under the executor's deadline kill instead, and the tool description advertises the hand-over only when it holds.
|
|
60
65
|
|
|
61
66
|
### Sandboxed execution and escalation
|
|
62
67
|
|
|
63
|
-
When the mounted executor confines commands (for example `dsh-bash-sandbox`), a blocked file operation is reported as `[sandbox: file access denied under <mode> mode]` — a policy denial, not a command failure. The model may then retry the exact same command once in the same turn with `sandbox_permissions` (the narrowest wider mode that suffices) and a one-sentence `justification`; the approval prompt raised by that retry is how the user consents.
|
|
68
|
+
When the mounted executor confines commands (for example `dsh-bash-sandbox`), a blocked file operation is reported as `[sandbox: file access denied under <mode> mode]` — a policy denial, not a command failure. The model may then retry the exact same command once in the same turn with `sandbox_permissions` (the narrowest wider mode that suffices) and a one-sentence `justification`; the approval prompt raised by that retry is how the user consents. Request wider access only after a real denial; a rejected escalation is final for that command. Repeating the current mode runs without approval, while a narrower target fails before execution. Without `sandbox_permissions`, `justification` may be omitted, empty, or whitespace-only; a non-empty reason without a mode is rejected. Repeating the effective mode also permits an omitted or blank reason. A different requested mode requires a non-empty reason; widening still requires approval.
|
|
64
69
|
|
|
65
70
|
### What can go wrong
|
|
66
71
|
|
|
@@ -81,14 +86,14 @@ This section explains the design decisions behind the tool and points at the cod
|
|
|
81
86
|
- **Model-facing consumer of the shell seam.** The tool is the Consumer role of the bash capability: it registers the `bash` schema, renders results, and resolves per-call policy, while the executor seam owns process mechanics.
|
|
82
87
|
- **Request from named args only.** The tool never exposes `stdin`, `env`, or `stdoutMaxBytes`; it builds each request from command/workdir/timeout/signal fields plus the registry-collected `dshEnv`, so model-supplied keys cannot replace managed values.
|
|
83
88
|
- **Non-zero exits are reported, not errored.** Only infrastructure failures (spawn errors, aborts) surface as tool errors; the model interprets exit codes and markers.
|
|
84
|
-
- **
|
|
89
|
+
- **Every command belongs to the job runtime when one is composed.** A call registers its process handle with `ctx.jobs` as it starts, whether the model asked for the background or the tool waits on it; ids, ownership, completion notices, and disposal are the runtime's, and this tool only maps bash exit and sandbox facts into job output. Without a registry the tool is foreground-only, and it swaps between the two registrations as the registry comes and goes.
|
|
85
90
|
|
|
86
91
|
### Source map
|
|
87
92
|
|
|
88
93
|
| File | Role |
|
|
89
94
|
|---|---|
|
|
90
95
|
| [`src/index.ts`](src/index.ts) | Plugin entry: tool registration, prompt section, arg validation, escalation, request assembly |
|
|
91
|
-
| [`src/background.ts`](src/background.ts) | Own asynchronous shell preparation
|
|
96
|
+
| [`src/background.ts`](src/background.ts) | Own asynchronous shell preparation, map process settlement onto job outcomes, and render a ring read as a process read |
|
|
92
97
|
| [`src/render.ts`](src/render.ts) | Model-facing result text: streams, markers, truncation notices |
|
|
93
98
|
| — | No runtime invariant companion is published; the environment registry validates ownership and collected values at each mutation/read; it publishes no independent snapshot that a companion could cross-check. |
|
|
94
99
|
|
|
@@ -146,7 +151,7 @@ Prefix-stable while the registration scope and prompt text are unchanged. Plugin
|
|
|
146
151
|
|
|
147
152
|
#### What the model sees
|
|
148
153
|
|
|
149
|
-
The model sees the generated [`bash` schema](../../../docs/tool-catalog.md#deepseek-aidsh-tool-bash). `run_in_background` appears only when this producer enables it; `sandbox_permissions` and `justification` appear only when the mounted executor advertises sandboxing. Agent-scoped tool restrictions can remove the definition for that agent.
|
|
154
|
+
The model sees the generated [`bash` schema](../../../docs/tool-catalog.md#deepseek-aidsh-tool-bash). `run_in_background` appears only when this producer enables it and a job registry is composed; `sandbox_permissions` and `justification` appear only when the mounted executor advertises sandboxing. Agent-scoped tool restrictions can remove the definition for that agent.
|
|
150
155
|
|
|
151
156
|
#### Token effect
|
|
152
157
|
|
|
@@ -160,7 +165,7 @@ Prefix-stable while visibility, background support, and executor sandbox capabil
|
|
|
160
165
|
|
|
161
166
|
#### What the model sees
|
|
162
167
|
|
|
163
|
-
The renderer emits the data-dependent stdout tail, then optional `[stderr]` and the stderr tail. With no output it emits exactly `(no output)`. Conditional lines are exactly `[output truncated; full output: <path-or-(unavailable)>]`, `[sandbox: file access denied under <mode> mode]`, `[timed out after <timeoutMs>ms]`, `[killed by signal: <signal>]`, and `[exit code: <exitCode>]`; the sandbox escalation and runner-failure lines are quoted in [`dsh-bash-sandbox`](../bash-sandbox/README.md).
|
|
168
|
+
The renderer emits the data-dependent stdout tail, then optional `[stderr]` and the stderr tail. With no output it emits exactly `(no output)`. Conditional lines are exactly `[output truncated; full output: <path-or-(unavailable)>]`, `[sandbox: file access denied under <mode> mode]`, `[timed out after <timeoutMs>ms]`, `[stopped: <reason>]`, `[killed by signal: <signal>]`, and `[exit code: <exitCode>]`; the sandbox escalation and runner-failure lines are quoted in [`dsh-bash-sandbox`](../bash-sandbox/README.md).
|
|
164
169
|
|
|
165
170
|
#### Token effect
|
|
166
171
|
|
|
@@ -207,7 +212,8 @@ These limits define when the tool is a poor fit or needs special care. They are
|
|
|
207
212
|
|
|
208
213
|
- **Replay exit pills parse from result text** — output whose final line happens to be exactly `[exit code: N]` / `[killed by signal: …]` shows a wrong pill on session replay and loses that line from the card body, because the parse treats it as the marker it consumes; a display-only known residual.
|
|
209
214
|
- **The `bash` tool opts out of `timeout-policy` budgets** — it keeps the executor-owned `BASH_TIMEOUT` path, per [the tool-call timeout-policy Agent Note](../../../.agents/notes/implemented/architecture/2026-07-07-tool-call-timeout-policy.md).
|
|
210
|
-
- **Background processes have no executor timeout** — callers must use `job_kill`, or rely on owner/service disposal, when work no longer matters.
|
|
215
|
+
- **Background processes have no executor timeout** — callers must use `job_kill`, or rely on owner/service disposal, when work no longer matters; a foreground command registered as a job has none either, since its timeout bounds only the wait.
|
|
216
|
+
- **The job list shows every foreground command while it runs** — a settled one leaves with its result, but the Web task list does not yet mark which running rows a tool call is still waiting on.
|
|
211
217
|
|
|
212
218
|
<a id="dev-note"></a>
|
|
213
219
|
### Dev Note
|
package/README.zh.md
CHANGED
|
@@ -9,7 +9,7 @@ kind: "package-reference"
|
|
|
9
9
|
|
|
10
10
|
## 概述
|
|
11
11
|
|
|
12
|
-
`dsh-tool-bash`
|
|
12
|
+
`dsh-tool-bash` 运行 Bash 命令并返回 stdout、stderr 与退出标记。每次调用使用全新 shell;cwd、变量和函数不会保留。组合中有 job 注册表时,每条命令从启动起就是一个 job:`run_in_background` 立即返回 id,超过超时仍在运行的前台命令返回同一个 id,`job_output`/`job_kill` 收集并停止它。命令获得受管 `DSH_*` 环境;沙箱拒绝可携带更宽的 `sandbox_permissions`、一句 `justification` 与用户批准重试一次。非零退出是供 agent(智能体)解读的结果。请将 `dsh-bash-local` 或 `dsh-bash-sandbox` 等执行器与 `dsh-shell-env` 一起挂载。
|
|
13
13
|
|
|
14
14
|
## 目录
|
|
15
15
|
|
|
@@ -41,11 +41,12 @@ kind: "package-reference"
|
|
|
41
41
|
- name: '@deepseek-ai/dsh-tool-jobs'
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
配置字段决定后台能力面。
|
|
45
45
|
|
|
46
46
|
| 字段 | 默认值 | 含义 |
|
|
47
47
|
|---|---|---|
|
|
48
|
-
| `enableRunInBackground` | `true` |
|
|
48
|
+
| `enableRunInBackground` | `true` | 组合中有 job 注册表时暴露 `run_in_background`;为 `false` 时拒绝强制后台调用 |
|
|
49
|
+
| `promoteOnTimeout` | `true` | 到达超时的前台命令继续作为它的后台任务运行,而不是杀掉它 |
|
|
49
50
|
|
|
50
51
|
生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-tool-bash)是每个受支持字段及其 JSDoc 的穷尽式真源;生成的[工具目录](../../../docs/tool-catalog.zh.md#deepseek-aidsh-tool-bash)携带完整参数 schema。
|
|
51
52
|
|
|
@@ -56,11 +57,15 @@ kind: "package-reference"
|
|
|
56
57
|
<a id="running-long-commands-in-the-background"></a>
|
|
57
58
|
### 后台运行长时间命令
|
|
58
59
|
|
|
59
|
-
传入 `run_in_background: true` 会准入任务并立即返回 job id;限制准备可能仍在进行,且不设后台执行超时。进程可用前输出为空。任务取消会中止准备并停止随后返回的进程;启动失败使已准入任务以失败状态结算。agent 用 `job_output` 读取输出(除非 `wait: true`,否则非阻塞)、用 `job_list` 列出任务、用 `job_kill` 停止任务;完成的任务会在会话内通知拥有它的 agent。后台支持需要挂载通用任务运行时(`dsh-jobs-local`)及其控制工具(`dsh-tool-jobs
|
|
60
|
+
传入 `run_in_background: true` 会准入任务并立即返回 job id;限制准备可能仍在进行,且不设后台执行超时。进程可用前输出为空。任务取消会中止准备并停止随后返回的进程;启动失败使已准入任务以失败状态结算。agent 用 `job_output` 读取输出(除非 `wait: true`,否则非阻塞)、用 `job_list` 列出任务、用 `job_kill` 停止任务;完成的任务会在会话内通知拥有它的 agent。后台支持需要挂载通用任务运行时(`dsh-jobs-local`)及其控制工具(`dsh-tool-jobs`)。后台 job 把运行的非消费 `observed` 读取器交给 job 注册表作为拉式源;注册表按自己的节奏(`dsh-jobs-local` 的 `pumpPollMs`)把它们泵入 job 的输出环,Web 客户端由此流式看到实时输出,模型的 `job_output` 读取则经另一个游标消费同一份字节。读取器抛错只记录一次,该流就此停止,job 继续跑到自己的结算。输出环是尽力而为的实时预览:stdout 与 stderr 按轮询轮次复制,同一个轮询窗口内两条流的写入会先 stdout 后 stderr 出现,而不是按写入顺序。
|
|
61
|
+
|
|
62
|
+
### 前台命令即任务
|
|
63
|
+
|
|
64
|
+
组合中有 job 注册表时,前台命令一启动就登记到 `ctx.jobs`,调用等待该任务:命令在运行期间始终被列出、经 `job.list` 与 `job.follow` 流式观看,并可从 Web 任务列表停止。在超时内完成的命令返回普通前台结果,其任务记录随结果一起离开注册表,模型从不看到 id。超过超时仍在运行的命令继续作为它本来就是的那个任务运行,调用返回 `[still running after <timeoutMs>ms; moved to background job <id>]` 加任务交接指引,并以一次消费式读取带上目前为止的输出——`job_output` 恰好从此处接续。来自调用之外的杀停(人在界面上停止任务)会让前台结果在信号标记之前带上 `[stopped: <reason>]`,模型读到的是原因而不是命令失败;取消调用本身则杀掉任务。登记是尽力而为的:`promoteOnTimeout: false`、缺少 job 注册表,或注册表在启动时拒绝该任务(持有者的任务上限、没有控制器)都会改为在执行器的 deadline 杀下运行命令,工具描述也只在交接语义成立时才宣传它。
|
|
60
65
|
|
|
61
66
|
### 沙箱执行与升权
|
|
62
67
|
|
|
63
|
-
当已挂载的执行器约束命令(例如 `dsh-bash-sandbox`)时,被阻止的文件操作会报告为 `[sandbox: file access denied under <mode> mode]`——这是策略拒绝,不是命令失败。模型随后可以在同一轮次中用 `sandbox_permissions`(满足需要的最窄更宽模式)与一句 `justification`
|
|
68
|
+
当已挂载的执行器约束命令(例如 `dsh-bash-sandbox`)时,被阻止的文件操作会报告为 `[sandbox: file access denied under <mode> mode]`——这是策略拒绝,不是命令失败。模型随后可以在同一轮次中用 `sandbox_permissions`(满足需要的最窄更宽模式)与一句 `justification` 重试完全相同的命令一次;该重试引发的审批提示就是用户同意的方式。只有发生真实拒绝后才请求更宽权限;被拒绝的升权对该命令即为最终结果。重复当前模式无需审批即可执行,更窄目标则在执行前失败。未提供 `sandbox_permissions` 时,`justification` 可以省略、为空字符串或仅含空白;未指定模式却提供非空理由会被拒绝。重复当前生效模式时也可省略理由或提供空白理由。请求不同模式时必须提供非空理由;升权仍需审批。
|
|
64
69
|
|
|
65
70
|
### 可能出什么问题
|
|
66
71
|
|
|
@@ -81,14 +86,14 @@ kind: "package-reference"
|
|
|
81
86
|
- **shell seam 的模型侧消费方。** 本工具是 bash 能力的消费方角色:它注册 `bash` schema、渲染结果并解析每次调用的策略,进程机制归执行器 seam 所有。
|
|
82
87
|
- **请求只来自命名参数。** 工具从不暴露 `stdin`、`env` 或 `stdoutMaxBytes`;它只用命令/workdir/超时/信号字段加上注册表收集的 `dshEnv` 构建每个请求,因此模型提供的键无法替换受管值。
|
|
83
88
|
- **非零退出只报告、不失败。** 只有基础设施故障(spawn 错误、中止)才会作为工具错误暴露;模型解读退出码与标记。
|
|
84
|
-
-
|
|
89
|
+
- **组合了任务运行时,每条命令都归它。** 调用一启动就把进程句柄注册到 `ctx.jobs`,无论是模型要求后台还是工具在等待它;job id、所有权、完成通知与释放都是运行时的职责,本工具只把 bash 退出与沙箱事实映射为任务输出。没有注册表时工具只做前台,并随注册表的出现与消失在两种注册之间切换。
|
|
85
90
|
|
|
86
91
|
### 源码地图
|
|
87
92
|
|
|
88
93
|
| 文件 | 职责 |
|
|
89
94
|
|---|---|
|
|
90
95
|
| [`src/index.ts`](src/index.ts) | 插件入口:工具注册、提示词区段、参数校验、升权、请求组装 |
|
|
91
|
-
| [`src/background.ts`](src/background.ts) | 管理异步 shell
|
|
96
|
+
| [`src/background.ts`](src/background.ts) | 管理异步 shell 准备、将进程结算映射为任务结果,并把输出环读取渲染为进程读取 |
|
|
92
97
|
| [`src/render.ts`](src/render.ts) | 模型侧结果文本:流、标记、截断通知 |
|
|
93
98
|
| — | 不发布运行时不变式伴生入口;环境注册表在每次变更和读取时校验所有权及收集值,且不发布可供伴生入口交叉核对的独立快照;执行关系由能力 seam 负责。 |
|
|
94
99
|
|
|
@@ -146,7 +151,7 @@ Check the [exit code: N] marker on every bash result; investigate failures befor
|
|
|
146
151
|
|
|
147
152
|
#### 模型看到什么
|
|
148
153
|
|
|
149
|
-
模型会看到生成的 [`bash` schema](../../../docs/tool-catalog.zh.md#deepseek-aidsh-tool-bash)。仅当本生产方启用 `run_in_background`
|
|
154
|
+
模型会看到生成的 [`bash` schema](../../../docs/tool-catalog.zh.md#deepseek-aidsh-tool-bash)。仅当本生产方启用 `run_in_background` 且组合中有 job 注册表时,该字段才会出现;仅当已挂载执行器声明支持沙箱时,`sandbox_permissions` 和 `justification` 才会出现。按 agent 作用域限制工具可以移除该 agent 的定义。
|
|
150
155
|
|
|
151
156
|
#### Token 影响
|
|
152
157
|
|
|
@@ -160,7 +165,7 @@ Check the [exit code: N] marker on every bash result; investigate failures befor
|
|
|
160
165
|
|
|
161
166
|
#### 模型看到什么
|
|
162
167
|
|
|
163
|
-
renderer 输出依数据而定的 stdout 尾部,再输出可选的 `[stderr]` 和 stderr 尾部。没有输出时,它精确输出 `(no output)`。条件行精确为 `[output truncated; full output: <path-or-(unavailable)>]`、`[sandbox: file access denied under <mode> mode]`、`[timed out after <timeoutMs>ms]`、`[killed by signal: <signal>]` 与 `[exit code: <exitCode>]`;沙箱升权与 runner 故障行原文列于 [`dsh-bash-sandbox`](../bash-sandbox/README.zh.md)。
|
|
168
|
+
renderer 输出依数据而定的 stdout 尾部,再输出可选的 `[stderr]` 和 stderr 尾部。没有输出时,它精确输出 `(no output)`。条件行精确为 `[output truncated; full output: <path-or-(unavailable)>]`、`[sandbox: file access denied under <mode> mode]`、`[timed out after <timeoutMs>ms]`、`[stopped: <reason>]`、`[killed by signal: <signal>]` 与 `[exit code: <exitCode>]`;沙箱升权与 runner 故障行原文列于 [`dsh-bash-sandbox`](../bash-sandbox/README.zh.md)。
|
|
164
169
|
|
|
165
170
|
#### Token 影响
|
|
166
171
|
|
|
@@ -207,7 +212,8 @@ renderer 输出依数据而定的 stdout 尾部,再输出可选的 `[stderr]`
|
|
|
207
212
|
|
|
208
213
|
- **回放的退出 pill 从结果文本解析**——输出最后一行恰好是 `[exit code: N]` / `[killed by signal: …]` 时,会话回放会显示错误的 pill 并从卡片正文丢失该行,因为解析把它当作要消费的标记;这是仅影响显示的已知残留。
|
|
209
214
|
- **`bash` 工具不参与 `timeout-policy` 预算**——它保留执行器自有的 `BASH_TIMEOUT` 路径,见[工具调用超时策略 Agent Note](../../../.agents/notes/implemented/architecture/2026-07-07-tool-call-timeout-policy.zh.md)。
|
|
210
|
-
- **后台进程没有执行器超时**——工作不再需要时,调用方必须使用 `job_kill
|
|
215
|
+
- **后台进程没有执行器超时**——工作不再需要时,调用方必须使用 `job_kill`,或依赖持有者/服务的释放;登记为任务的前台命令同样没有,它的超时只限定等待时长。
|
|
216
|
+
- **任务列表会显示每条运行中的前台命令**——完成的命令随结果离开,但 Web 任务列表尚未标出哪些运行中的行仍有工具调用在等待。
|
|
211
217
|
|
|
212
218
|
<a id="dev-note"></a>
|
|
213
219
|
### 开发备注
|
package/lib/index.js
CHANGED
|
@@ -6,35 +6,98 @@ import { ESCALATION_TARGETS, approveEscalation, escalationHintMarker, sandboxDen
|
|
|
6
6
|
import { DSH_ENV_PREFIX, parseExitStatus } from "@deepseek-ai/dsh-shell";
|
|
7
7
|
//#region lib/types/background.js
|
|
8
8
|
/**
|
|
9
|
-
* Generic-
|
|
9
|
+
* Generic-job adaptation for bash process handles: the terminal outcome the
|
|
10
|
+
* registry records, the pull sources it pumps, and the ring read a foreground
|
|
11
|
+
* call renders when it stops waiting.
|
|
10
12
|
*
|
|
11
13
|
* @module @deepseek-ai/dsh-tool-bash/background
|
|
12
14
|
*/
|
|
13
15
|
/**
|
|
14
|
-
*
|
|
16
|
+
* Sandbox facts worth the terminal detail: a runner that never ran the
|
|
17
|
+
* command, or a denial (with the escalation hint this composition offers).
|
|
18
|
+
* @param sandbox - settled sandbox facts, when this was a confined process.
|
|
19
|
+
* @param escalationModes - escalation targets advertised by this composition.
|
|
20
|
+
* @returns the markers to append, oldest first.
|
|
21
|
+
*/
|
|
22
|
+
function sandboxNotes(sandbox, escalationModes) {
|
|
23
|
+
if (sandbox?.runnerFailed) return [`[sandbox: the sandbox runner itself failed under ${sandbox.mode} mode — the command did not run; this is a sandbox problem, not a command failure]`];
|
|
24
|
+
if (sandbox?.denied) {
|
|
25
|
+
const notes = [sandboxDenialMarker(sandbox.mode)];
|
|
26
|
+
if (escalationModes.length > 0) notes.push(escalationHintMarker("command"));
|
|
27
|
+
return notes;
|
|
28
|
+
}
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Map a settled background process onto the generic job-outcome vocabulary:
|
|
15
33
|
* `killed` stays `killed` (detail: the signal when one is known), everything
|
|
16
34
|
* else is `completed` with the exit code as detail. A nonzero command exit is
|
|
17
|
-
* reported, not failed, exactly like the foreground rendering.
|
|
35
|
+
* reported, not failed, exactly like the foreground rendering. Sandbox facts
|
|
36
|
+
* join the detail, since a job's terminal reason is the one line every
|
|
37
|
+
* reader — the model's status line, the roster row — shows.
|
|
18
38
|
* @param proc - the settled process handle.
|
|
39
|
+
* @param escalationModes - escalation targets advertised by this composition.
|
|
19
40
|
* @returns the outcome for the `ctx.jobs` registration.
|
|
20
41
|
*/
|
|
21
|
-
function processOutcome(proc) {
|
|
22
|
-
|
|
42
|
+
function processOutcome(proc, escalationModes = []) {
|
|
43
|
+
const base = proc.status === "killed" ? {
|
|
23
44
|
status: "killed",
|
|
24
45
|
detail: proc.signal !== null ? `signal: ${proc.signal}` : "killed before exit"
|
|
25
|
-
}
|
|
26
|
-
return {
|
|
46
|
+
} : {
|
|
27
47
|
status: "completed",
|
|
28
48
|
detail: `exit code: ${proc.exitCode ?? 0}`
|
|
29
49
|
};
|
|
50
|
+
const notes = sandboxNotes(proc.sandbox, escalationModes);
|
|
51
|
+
return notes.length === 0 ? base : {
|
|
52
|
+
...base,
|
|
53
|
+
detail: `${base.detail}; ${notes.join(" ")}`
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The process's non-consuming stream readers as registry pull sources. They
|
|
58
|
+
* bind lazily because the process is spawned inside the starter, after the
|
|
59
|
+
* registry admitted the job; a read before the spawn yields nothing, and the
|
|
60
|
+
* pump keeps the model's consuming cursor untouched. A rejected spawn's
|
|
61
|
+
* stderr reader carries the provider's `subprocess failed before reporting an
|
|
62
|
+
* outcome: …` note.
|
|
63
|
+
* @param proc - the started process's observed streams, once the starter has spawned it.
|
|
64
|
+
* @returns one source per stream, stdout first.
|
|
65
|
+
*/
|
|
66
|
+
function processSources(proc) {
|
|
67
|
+
const source = (channel) => ({
|
|
68
|
+
channel,
|
|
69
|
+
read: (fromByte) => {
|
|
70
|
+
const live = proc();
|
|
71
|
+
return live === void 0 ? {
|
|
72
|
+
text: "",
|
|
73
|
+
nextOffset: fromByte,
|
|
74
|
+
lossy: false
|
|
75
|
+
} : live.observed[channel].readFrom(fromByte);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
return [source("stdout"), source("stderr")];
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The ring chunks of one consuming registry read as the shell tools render a
|
|
82
|
+
* process read: stdout chunks in order, then every stderr chunk in one
|
|
83
|
+
* `[stderr]` section, so the output a foreground call hands over when it
|
|
84
|
+
* stops waiting reads exactly like the `job_output` reads that follow it.
|
|
85
|
+
* @param chunks - the chunks since the model cursor, in offset order.
|
|
86
|
+
* @returns the delta text, possibly empty.
|
|
87
|
+
*/
|
|
88
|
+
function ringDelta(chunks) {
|
|
89
|
+
const out = chunks.filter((chunk) => chunk.channel !== "stderr").map((chunk) => chunk.text).join("");
|
|
90
|
+
const err = chunks.filter((chunk) => chunk.channel === "stderr").map((chunk) => chunk.text).join("");
|
|
91
|
+
const separator = out.length > 0 && !out.endsWith("\n") ? "\n" : "";
|
|
92
|
+
return out + (err.length > 0 ? `${separator}[stderr]\n${err}` : "");
|
|
30
93
|
}
|
|
31
94
|
/**
|
|
32
95
|
* Adapt asynchronous shell preparation after job admission without exposing a partial process.
|
|
33
96
|
* @param start - starts the process with job-owned cancellation.
|
|
34
|
-
* @param
|
|
97
|
+
* @param outcome - projects the settled process into the job outcome.
|
|
35
98
|
* @returns synchronous job hooks whose completion includes preparation and process settlement.
|
|
36
99
|
*/
|
|
37
|
-
function processJob(start,
|
|
100
|
+
function processJob(start, outcome) {
|
|
38
101
|
const controller = new AbortController();
|
|
39
102
|
let process;
|
|
40
103
|
return {
|
|
@@ -51,15 +114,14 @@ function processJob(start, renderOutput) {
|
|
|
51
114
|
} finally {
|
|
52
115
|
await process.done;
|
|
53
116
|
}
|
|
54
|
-
return
|
|
117
|
+
return outcome(process);
|
|
55
118
|
} catch (error) {
|
|
56
119
|
return {
|
|
57
120
|
status: controller.signal.aborted && process === void 0 ? "killed" : "failed",
|
|
58
121
|
detail: error instanceof Error ? error.message : String(error)
|
|
59
122
|
};
|
|
60
123
|
}
|
|
61
|
-
})()
|
|
62
|
-
readOutput: () => process === void 0 ? "" : renderOutput(process)
|
|
124
|
+
})()
|
|
63
125
|
};
|
|
64
126
|
}
|
|
65
127
|
//#endregion
|
|
@@ -83,7 +145,7 @@ function streamText(output) {
|
|
|
83
145
|
* @param escalationModes - the escalation targets this composition advertises;
|
|
84
146
|
* non-empty adds the same-turn escalation hint after a denial marker
|
|
85
147
|
* (default `[]`: no hint).
|
|
86
|
-
* @returns the model-facing text: output body (or `(no output)`), then any timeout/signal/exit markers, each on its own line.
|
|
148
|
+
* @returns the model-facing text: output body (or `(no output)`), then any timeout/stopped/signal/exit markers, each on its own line.
|
|
87
149
|
*/
|
|
88
150
|
function renderResult(result, escalationModes = []) {
|
|
89
151
|
const out = streamText(result.stdout);
|
|
@@ -100,6 +162,7 @@ function renderResult(result, escalationModes = []) {
|
|
|
100
162
|
if (escalationModes.length > 0) markers.push(escalationHintMarker("command"));
|
|
101
163
|
}
|
|
102
164
|
if (result.timedOut) markers.push(`[timed out after ${result.timeoutMs}ms]`);
|
|
165
|
+
if (result.stopped !== void 0) markers.push(`[stopped: ${result.stopped}]`);
|
|
103
166
|
if (result.signal !== null) markers.push(`[killed by signal: ${result.signal}]`);
|
|
104
167
|
else if (result.exitCode !== 0) markers.push(`[exit code: ${result.exitCode}]`);
|
|
105
168
|
if (markers.length === 0) return body;
|
|
@@ -107,35 +170,50 @@ function renderResult(result, escalationModes = []) {
|
|
|
107
170
|
return body + markers.join("\n");
|
|
108
171
|
}
|
|
109
172
|
/**
|
|
110
|
-
* Shape
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
* @param
|
|
173
|
+
* Shape a foreground call that stopped waiting into the text the model sees:
|
|
174
|
+
* the output captured so far (one consuming registry read taken at that
|
|
175
|
+
* point, so `job_output` continues exactly after it), then the still-running
|
|
176
|
+
* marker and the job hand-off guidance.
|
|
177
|
+
* @param promoted - the promoted result value: the job id, the wait that
|
|
178
|
+
* expired, and the output so far.
|
|
179
|
+
* @returns the model-facing text for a promoted call.
|
|
180
|
+
*/
|
|
181
|
+
function renderPromoted(promoted) {
|
|
182
|
+
return `${promoted.output.length > 0 ? promoted.output.endsWith("\n") ? promoted.output : `${promoted.output}\n` : ""}[still running after ${promoted.timeoutMs}ms; moved to background job ${promoted.jobId}]\nThe command keeps running in the background. You will be notified when it finishes; read newer output with job_output, stop it with job_kill.`;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Shape the one consuming registry read a foreground call embeds in its
|
|
186
|
+
* result when it stops waiting: the output produced so far, plus the
|
|
187
|
+
* dropped-output notice (naming the job's spill files) when the model cursor
|
|
188
|
+
* fell behind the ring, and the sandbox notices. Later `job_output` reads
|
|
189
|
+
* render the same ring through the job tools.
|
|
190
|
+
* @param delta - the read's chunks as rendered text.
|
|
191
|
+
* @param lossy - whether bytes before the delta were evicted unread.
|
|
192
|
+
* @param spillPaths - the complete-stream files the job currently advertises.
|
|
115
193
|
* @param sandbox - settled sandbox facts, when this was a confined process.
|
|
116
194
|
* @param escalationModes - escalation targets advertised by this composition.
|
|
117
195
|
* @returns the delta text with any loss or sandbox notice appended.
|
|
118
196
|
*/
|
|
119
|
-
function
|
|
197
|
+
function renderJobRead(delta, lossy, spillPaths, sandbox, escalationModes = []) {
|
|
120
198
|
const notices = [];
|
|
121
|
-
if (
|
|
122
|
-
const paths = [read.stdoutSpillPath, read.stderrSpillPath].filter((path) => path !== void 0);
|
|
123
|
-
notices.push(`[some output was dropped from memory; full output: ${paths.length > 0 ? paths.join(", ") : "(unavailable)"}]`);
|
|
124
|
-
}
|
|
199
|
+
if (lossy) notices.push(`[some output was dropped from memory; full output: ${spillPaths.length > 0 ? spillPaths.join(", ") : "(unavailable)"}]`);
|
|
125
200
|
if (sandbox?.runnerFailed) 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]`);
|
|
126
201
|
else if (sandbox?.denied) {
|
|
127
202
|
notices.push(sandboxDenialMarker(sandbox.mode));
|
|
128
203
|
if (escalationModes.length > 0) notices.push(escalationHintMarker("command"));
|
|
129
204
|
}
|
|
130
|
-
if (notices.length === 0) return
|
|
131
|
-
return `${
|
|
205
|
+
if (notices.length === 0) return delta;
|
|
206
|
+
return `${delta}${delta.length > 0 && !delta.endsWith("\n") ? "\n" : ""}${notices.join("\n")}`;
|
|
132
207
|
}
|
|
133
208
|
//#endregion
|
|
134
209
|
//#region lib/types/index.js
|
|
135
210
|
/**
|
|
136
|
-
* Model-facing Consumer of the `ctx.shell` capability seam.
|
|
137
|
-
*
|
|
138
|
-
*
|
|
211
|
+
* Model-facing Consumer of the `ctx.shell` capability seam. While a job
|
|
212
|
+
* registry is composed, every call registers its process with `ctx.jobs` as
|
|
213
|
+
* it starts: `run_in_background` returns the id at once, and a foreground call
|
|
214
|
+
* waits on its job until the command finishes or the wait times out, at which
|
|
215
|
+
* point it returns the same id. Without a registry the tool is foreground-only
|
|
216
|
+
* and the executor's deadline kills the command.
|
|
139
217
|
*
|
|
140
218
|
* TODO(permissions): deployment policy belongs in `tools/pre-execute` and
|
|
141
219
|
* sandboxing executors; see docs/architecture.md § Where new behavior goes.
|
|
@@ -149,15 +227,20 @@ const inject = [
|
|
|
149
227
|
"shellEnv"
|
|
150
228
|
];
|
|
151
229
|
/** Runtime configuration schema for the bash tool plugin. */
|
|
152
|
-
const Config = z.object({
|
|
153
|
-
|
|
230
|
+
const Config = z.object({
|
|
231
|
+
enableRunInBackground: z.boolean().default(true),
|
|
232
|
+
promoteOnTimeout: z.boolean().default(true)
|
|
233
|
+
});
|
|
234
|
+
function validateBashArgs(args, effectiveMode) {
|
|
154
235
|
if (args.command.trim().length === 0) throw new Error("invalid command: expected a non-empty string");
|
|
155
236
|
if (args.description.trim().length === 0) throw new Error("invalid description: expected a non-empty string");
|
|
156
237
|
if (args.timeoutMs !== void 0 && (!Number.isFinite(args.timeoutMs) || args.timeoutMs <= 0)) throw new Error(`invalid timeoutMs: expected a positive number, got ${JSON.stringify(args.timeoutMs)}`);
|
|
157
|
-
|
|
238
|
+
if (args.sandbox_permissions !== void 0 && args.sandbox_permissions === effectiveMode) return;
|
|
239
|
+
const justification = args.sandbox_permissions === void 0 && args.justification?.trim() === "" ? void 0 : args.justification;
|
|
240
|
+
validateEscalationArgs(args.sandbox_permissions, justification);
|
|
158
241
|
}
|
|
159
|
-
function bashDescription(backgroundEnabled, escalationModes) {
|
|
160
|
-
const background = backgroundEnabled ? "Set `run_in_background: true` for long-running commands: the call returns a job id immediately; read its output with `job_output` and stop it with `job_kill`." : "Background execution is not available; long-running commands must finish within the timeout.";
|
|
242
|
+
function bashDescription(backgroundEnabled, escalationModes, promoteOnTimeout) {
|
|
243
|
+
const background = backgroundEnabled ? "Set `run_in_background: true` for long-running commands: the call returns a job id immediately; read its output with `job_output` and stop it with `job_kill`." + (promoteOnTimeout ? " A foreground command that reaches its timeout is not killed: it moves to the background the same way, returning its job id and the output so far." : "") : "Background execution is not available; long-running commands must finish within the timeout.";
|
|
161
244
|
const base = `Execute a bash command (\`bash -c\`) and return its stdout/stderr. Each call runs in a fresh shell: no state (cwd, variables, functions) persists between calls — pass \`workdir\` instead of using \`cd\`. Non-zero exits are reported as \`[exit code: N]\`. Current harness environment facts are exposed through managed \`$${DSH_ENV_PREFIX}*\` variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as \`[sandbox: file access denied under <mode> mode]\` — a policy denial, not a bug in the command; do not retry another way. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. ` + background;
|
|
162
245
|
if (escalationModes.length === 0) return base;
|
|
163
246
|
return base + " Attempting a command the sandbox may deny is safe and expected: run it and read the marker rather than assuming the denial. When a command is denied and a wider mode would let it succeed, escalate immediately in the same turn — the one sanctioned exception to a denial: retry the exact same command once with `sandbox_permissions` (the narrowest wider mode that suffices) plus a one-sentence `justification`. Do not detour through chat to ask permission first — the approval prompt raised by that retry is how the user consents. If the session states approval prompts are disabled, there is no exception: a denial is final — do not set `sandbox_permissions`. Never escalate speculatively: ground the request in a real denial — normally the one this command just hit; escalating up front is fine only when this session already denied the same access. A rejected escalation is final for that command — stop and explain, never work around it — but it does not forbid attempting or escalating other commands later.";
|
|
@@ -188,7 +271,9 @@ function presentBashResult(args, result) {
|
|
|
188
271
|
const block = result.content.length === 1 ? result.content[0] : void 0;
|
|
189
272
|
if (block === void 0 || block.type !== "text") return void 0;
|
|
190
273
|
const raw = block.text;
|
|
191
|
-
|
|
274
|
+
const isBackground = typeof args === "object" && args !== null && args.run_in_background === true;
|
|
275
|
+
const isPromoted = result.value?.kind === "promoted";
|
|
276
|
+
if (isBackground || isPromoted || result.isError) return {
|
|
192
277
|
card: "generic",
|
|
193
278
|
content: [{
|
|
194
279
|
type: "text",
|
|
@@ -238,6 +323,12 @@ function canonicalBashResult(result) {
|
|
|
238
323
|
} } : {}
|
|
239
324
|
};
|
|
240
325
|
}
|
|
326
|
+
/** The structured abort the foreground paths throw when the caller cancels the call. */
|
|
327
|
+
function toolAborted() {
|
|
328
|
+
const error = new HarnessError("tool call aborted", TOOL_ABORTED);
|
|
329
|
+
error.name = "AbortError";
|
|
330
|
+
return error;
|
|
331
|
+
}
|
|
241
332
|
/** Canonical background-handle properties shared by the bash output union. */
|
|
242
333
|
const BACKGROUND_OUTPUT_PROPERTIES = {
|
|
243
334
|
kind: {
|
|
@@ -252,6 +343,7 @@ const BACKGROUND_OUTPUT_PROPERTIES = {
|
|
|
252
343
|
};
|
|
253
344
|
function apply(ctx, config = {}) {
|
|
254
345
|
const backgroundEnabled = config.enableRunInBackground ?? true;
|
|
346
|
+
const promoteOnTimeout = (config.promoteOnTimeout ?? true) && backgroundEnabled;
|
|
255
347
|
const defaultMode = ctx.shell.sandboxMode;
|
|
256
348
|
const escalationModes = defaultMode === void 0 ? [] : ESCALATION_TARGETS;
|
|
257
349
|
const sandboxPolicy = defaultMode === void 0 ? void 0 : ctx.get("sandboxPolicy");
|
|
@@ -290,189 +382,335 @@ function apply(ctx, config = {}) {
|
|
|
290
382
|
order: ctx.systemPrompt.getSectionOrder("TOOL_BASH"),
|
|
291
383
|
text: "Check the [exit code: N] marker on every bash result; investigate failures before moving on."
|
|
292
384
|
});
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
385
|
+
/**
|
|
386
|
+
* One registration of the `bash` tool. With a registry, every call
|
|
387
|
+
* registers its process as a job at its start; without one the tool is
|
|
388
|
+
* foreground-only and the executor's deadline kills the command.
|
|
389
|
+
*/
|
|
390
|
+
const bashTool = (jobs) => {
|
|
391
|
+
const background = jobs !== void 0;
|
|
392
|
+
const promote = background && promoteOnTimeout;
|
|
393
|
+
/** Register the command as a job; the process spawns inside the starter, after admission. */
|
|
394
|
+
const startJob = (registry, args, exec, spec) => {
|
|
395
|
+
let proc;
|
|
396
|
+
let stopped;
|
|
397
|
+
return {
|
|
398
|
+
id: registry.start({
|
|
399
|
+
kind: "bash",
|
|
400
|
+
label: args.command,
|
|
401
|
+
...exec.agent ? { owner: exec.agent.id } : {},
|
|
402
|
+
output: processSources(() => proc),
|
|
403
|
+
run: () => {
|
|
404
|
+
const hooks = processJob(async (signal) => {
|
|
405
|
+
proc = await ctx.shell.execute({
|
|
406
|
+
...spec,
|
|
407
|
+
signal
|
|
408
|
+
});
|
|
409
|
+
return proc;
|
|
410
|
+
}, (started) => processOutcome(started, escalationModes));
|
|
411
|
+
return {
|
|
412
|
+
done: hooks.done,
|
|
413
|
+
cancel: (reason) => {
|
|
414
|
+
stopped = reason;
|
|
415
|
+
hooks.cancel(reason);
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
}),
|
|
420
|
+
process: () => proc,
|
|
421
|
+
stopped: () => stopped
|
|
422
|
+
};
|
|
423
|
+
};
|
|
424
|
+
/** Wait on a registered foreground command until it settles or the timeout passes. */
|
|
425
|
+
const waitOnJob = async (registry, attached, exec, spec) => {
|
|
426
|
+
const owner = exec.agent?.id;
|
|
427
|
+
const timeoutMs = spec.timeoutMs;
|
|
428
|
+
/**
|
|
429
|
+
* Stop the job on this call's own account and stay on it until it
|
|
430
|
+
* settles, so the settlement is `awaited` and no completion notice
|
|
431
|
+
* follows a result this call already carries; the record then leaves
|
|
432
|
+
* with the call, as the model never saw the id.
|
|
433
|
+
*/
|
|
434
|
+
const stop = async (reason) => {
|
|
435
|
+
registry.kill(attached.id, owner, reason);
|
|
436
|
+
const settled = await registry.wait(attached.id, timeoutMs, owner);
|
|
437
|
+
if (settled.status !== "running" && settled.status !== "stopping") registry.remove(attached.id, owner);
|
|
438
|
+
return settled;
|
|
439
|
+
};
|
|
440
|
+
let view;
|
|
441
|
+
try {
|
|
442
|
+
view = await registry.wait(attached.id, timeoutMs, owner, exec.signal);
|
|
443
|
+
} catch {
|
|
444
|
+
await stop("tool call aborted");
|
|
445
|
+
throw toolAborted();
|
|
446
|
+
}
|
|
447
|
+
if ((view.status === "running" || view.status === "stopping") && attached.process() === void 0) {
|
|
448
|
+
await stop("timed out during preparation");
|
|
449
|
+
return {
|
|
450
|
+
kind: "foreground",
|
|
451
|
+
exitCode: null,
|
|
452
|
+
signal: null,
|
|
453
|
+
timedOut: true,
|
|
454
|
+
aborted: false,
|
|
455
|
+
timeoutMs,
|
|
456
|
+
stdout: {
|
|
457
|
+
text: "",
|
|
458
|
+
truncated: false
|
|
459
|
+
},
|
|
460
|
+
stderr: {
|
|
461
|
+
text: "",
|
|
462
|
+
truncated: false
|
|
463
|
+
},
|
|
464
|
+
...spec.sandboxPolicy !== void 0 ? { sandbox: {
|
|
465
|
+
mode: spec.sandboxPolicy.mode,
|
|
466
|
+
denied: false
|
|
467
|
+
} } : {}
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
if (view.status === "running" || view.status === "stopping") {
|
|
471
|
+
const read = registry.read(attached.id, owner);
|
|
472
|
+
return {
|
|
473
|
+
kind: "promoted",
|
|
474
|
+
jobId: attached.id,
|
|
475
|
+
timeoutMs,
|
|
476
|
+
output: renderJobRead(ringDelta(read.chunks), read.lossy, read.job.output.spillPaths ?? [], attached.process()?.sandbox, escalationModes)
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
registry.remove(attached.id, owner);
|
|
480
|
+
const process = attached.process();
|
|
481
|
+
if (process === void 0) throw new Error(view.detail);
|
|
482
|
+
const result = await process.result();
|
|
483
|
+
const stopped = attached.stopped();
|
|
484
|
+
return {
|
|
485
|
+
kind: "foreground",
|
|
486
|
+
...canonicalBashResult(result),
|
|
487
|
+
...stopped !== void 0 ? { stopped } : {}
|
|
488
|
+
};
|
|
489
|
+
};
|
|
490
|
+
return defineTool({
|
|
491
|
+
name: "bash",
|
|
492
|
+
description: bashDescription(background, escalationModes, promote),
|
|
493
|
+
parameters: {
|
|
494
|
+
command: {
|
|
321
495
|
type: "string",
|
|
322
|
-
|
|
323
|
-
description: "The
|
|
496
|
+
required: true,
|
|
497
|
+
description: "The bash command to execute."
|
|
324
498
|
},
|
|
325
|
-
|
|
499
|
+
description: {
|
|
326
500
|
type: "string",
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
501
|
+
required: true,
|
|
502
|
+
description: "Clear, concise description of what this command does in active voice, 5-10 words (shown in the UI). Examples: \"ls\" → \"List files in current directory\"; \"git status\" → \"Show working tree status\"; \"npm install\" → \"Install package dependencies\"."
|
|
503
|
+
},
|
|
504
|
+
timeoutMs: {
|
|
505
|
+
type: "number",
|
|
506
|
+
description: promote ? "Timeout in milliseconds. The executor applies its configured default and cap; on expiry the command moves to the background as a job instead of being killed." : "Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry."
|
|
507
|
+
},
|
|
508
|
+
workdir: {
|
|
509
|
+
type: "string",
|
|
510
|
+
description: "Working directory for this command. Defaults to the session workspace; a relative path is resolved against it."
|
|
511
|
+
},
|
|
512
|
+
...background ? { run_in_background: {
|
|
513
|
+
type: "boolean",
|
|
514
|
+
description: "Run in the background and return a job id immediately (collect with job_output, stop with job_kill). No timeout applies."
|
|
515
|
+
} } : {},
|
|
516
|
+
...escalationModes.length > 0 ? {
|
|
517
|
+
sandbox_permissions: {
|
|
341
518
|
type: "string",
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
},
|
|
345
|
-
exitCode: {
|
|
346
|
-
required: true,
|
|
347
|
-
oneOf: [{ type: "integer" }, { type: "null" }]
|
|
348
|
-
},
|
|
349
|
-
signal: {
|
|
350
|
-
required: true,
|
|
351
|
-
oneOf: [{ type: "string" }, { type: "null" }]
|
|
352
|
-
},
|
|
353
|
-
timedOut: {
|
|
354
|
-
type: "boolean",
|
|
355
|
-
required: true
|
|
519
|
+
enum: [...escalationModes],
|
|
520
|
+
description: "The wider sandbox mode this command needs. Only valid as a one-shot retry of a command the sandbox just denied; requires justification and user approval."
|
|
356
521
|
},
|
|
357
|
-
|
|
358
|
-
type: "
|
|
359
|
-
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
522
|
+
justification: {
|
|
523
|
+
type: "string",
|
|
524
|
+
description: "Required with sandbox_permissions: one sentence for the user explaining why this exact command needs the wider access."
|
|
525
|
+
}
|
|
526
|
+
} : {}
|
|
527
|
+
},
|
|
528
|
+
output: {
|
|
529
|
+
schema: { oneOf: [
|
|
530
|
+
{
|
|
531
|
+
type: "object",
|
|
532
|
+
additionalProperties: false,
|
|
533
|
+
properties: BACKGROUND_OUTPUT_PROPERTIES
|
|
364
534
|
},
|
|
365
|
-
|
|
535
|
+
{
|
|
366
536
|
type: "object",
|
|
367
537
|
additionalProperties: false,
|
|
368
|
-
required: true,
|
|
369
538
|
properties: {
|
|
370
|
-
|
|
539
|
+
kind: {
|
|
540
|
+
type: "string",
|
|
541
|
+
required: true,
|
|
542
|
+
const: "promoted"
|
|
543
|
+
},
|
|
544
|
+
jobId: {
|
|
371
545
|
type: "string",
|
|
372
546
|
required: true
|
|
373
547
|
},
|
|
374
|
-
|
|
375
|
-
type: "
|
|
548
|
+
timeoutMs: {
|
|
549
|
+
type: "number",
|
|
376
550
|
required: true
|
|
377
551
|
},
|
|
378
|
-
|
|
552
|
+
output: {
|
|
553
|
+
type: "string",
|
|
554
|
+
required: true
|
|
555
|
+
}
|
|
379
556
|
}
|
|
380
557
|
},
|
|
381
|
-
|
|
558
|
+
{
|
|
382
559
|
type: "object",
|
|
383
560
|
additionalProperties: false,
|
|
384
|
-
required: true,
|
|
385
561
|
properties: {
|
|
386
|
-
|
|
562
|
+
kind: {
|
|
387
563
|
type: "string",
|
|
388
|
-
required: true
|
|
564
|
+
required: true,
|
|
565
|
+
const: "foreground"
|
|
566
|
+
},
|
|
567
|
+
exitCode: {
|
|
568
|
+
required: true,
|
|
569
|
+
oneOf: [{ type: "integer" }, { type: "null" }]
|
|
570
|
+
},
|
|
571
|
+
signal: {
|
|
572
|
+
required: true,
|
|
573
|
+
oneOf: [{ type: "string" }, { type: "null" }]
|
|
389
574
|
},
|
|
390
|
-
|
|
575
|
+
timedOut: {
|
|
391
576
|
type: "boolean",
|
|
392
577
|
required: true
|
|
393
578
|
},
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
},
|
|
397
|
-
sandbox: {
|
|
398
|
-
type: "object",
|
|
399
|
-
additionalProperties: false,
|
|
400
|
-
properties: {
|
|
401
|
-
mode: {
|
|
402
|
-
type: "string",
|
|
579
|
+
aborted: {
|
|
580
|
+
type: "boolean",
|
|
403
581
|
required: true
|
|
404
582
|
},
|
|
405
|
-
|
|
406
|
-
|
|
583
|
+
stopped: { type: "string" },
|
|
584
|
+
timeoutMs: {
|
|
585
|
+
type: "number",
|
|
407
586
|
required: true
|
|
408
587
|
},
|
|
409
|
-
|
|
410
|
-
|
|
588
|
+
stdout: {
|
|
589
|
+
type: "object",
|
|
590
|
+
additionalProperties: false,
|
|
591
|
+
required: true,
|
|
592
|
+
properties: {
|
|
593
|
+
text: {
|
|
594
|
+
type: "string",
|
|
595
|
+
required: true
|
|
596
|
+
},
|
|
597
|
+
truncated: {
|
|
598
|
+
type: "boolean",
|
|
599
|
+
required: true
|
|
600
|
+
},
|
|
601
|
+
spillPath: { type: "string" }
|
|
602
|
+
}
|
|
603
|
+
},
|
|
604
|
+
stderr: {
|
|
605
|
+
type: "object",
|
|
606
|
+
additionalProperties: false,
|
|
607
|
+
required: true,
|
|
608
|
+
properties: {
|
|
609
|
+
text: {
|
|
610
|
+
type: "string",
|
|
611
|
+
required: true
|
|
612
|
+
},
|
|
613
|
+
truncated: {
|
|
614
|
+
type: "boolean",
|
|
615
|
+
required: true
|
|
616
|
+
},
|
|
617
|
+
spillPath: { type: "string" }
|
|
618
|
+
}
|
|
619
|
+
},
|
|
620
|
+
sandbox: {
|
|
621
|
+
type: "object",
|
|
622
|
+
additionalProperties: false,
|
|
623
|
+
properties: {
|
|
624
|
+
mode: {
|
|
625
|
+
type: "string",
|
|
626
|
+
required: true
|
|
627
|
+
},
|
|
628
|
+
denied: {
|
|
629
|
+
type: "boolean",
|
|
630
|
+
required: true
|
|
631
|
+
},
|
|
632
|
+
enforcement: { type: "string" },
|
|
633
|
+
runnerFailed: { type: "boolean" }
|
|
634
|
+
}
|
|
635
|
+
}
|
|
411
636
|
}
|
|
412
637
|
}
|
|
638
|
+
] },
|
|
639
|
+
render: (_args, value) => [{
|
|
640
|
+
type: "text",
|
|
641
|
+
text: value.kind === "background" ? `started background job ${value.jobId}` : value.kind === "promoted" ? renderPromoted(value) : renderResult(value, escalationModes)
|
|
642
|
+
}]
|
|
643
|
+
},
|
|
644
|
+
async execute(args, exec) {
|
|
645
|
+
const standingPolicy = resolveSandboxPolicy(exec);
|
|
646
|
+
validateBashArgs(args, standingPolicy?.mode);
|
|
647
|
+
const approvedMode = args.sandbox_permissions !== void 0 && args.justification !== void 0 ? await approveBashEscalation(args.sandbox_permissions, args.justification, exec, standingPolicy) : void 0;
|
|
648
|
+
const policy = approvedMode === void 0 ? standingPolicy : {
|
|
649
|
+
...standingPolicy,
|
|
650
|
+
mode: approvedMode
|
|
651
|
+
};
|
|
652
|
+
const workdir = resolveWorkdir(args.workdir, exec, standingPolicy?.workspaceRoot);
|
|
653
|
+
const dshEnv = ctx.shellEnv.collect(exec);
|
|
654
|
+
const request = {
|
|
655
|
+
command: args.command,
|
|
656
|
+
...workdir !== void 0 ? { workdir } : {},
|
|
657
|
+
...args.timeoutMs !== void 0 ? { timeoutMs: args.timeoutMs } : {},
|
|
658
|
+
dshEnv,
|
|
659
|
+
...policy !== void 0 ? { sandboxPolicy: policy } : {}
|
|
660
|
+
};
|
|
661
|
+
if (args.run_in_background === true) {
|
|
662
|
+
if (!backgroundEnabled) throw new Error("run_in_background is disabled for this deployment (enableRunInBackground: false)");
|
|
663
|
+
if (jobs === void 0) throw new Error("background jobs unavailable: load @deepseek-ai/dsh-jobs and @deepseek-ai/dsh-tool-jobs");
|
|
664
|
+
if (exec.signal.aborted) throw toolAborted();
|
|
665
|
+
return {
|
|
666
|
+
kind: "background",
|
|
667
|
+
jobId: startJob(jobs, args, exec, ctx.shell.resolve({
|
|
668
|
+
...request,
|
|
669
|
+
onExpiry: "none"
|
|
670
|
+
})).id
|
|
671
|
+
};
|
|
413
672
|
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
mode: approvedMode
|
|
427
|
-
};
|
|
428
|
-
const workdir = resolveWorkdir(args.workdir, exec, standingPolicy?.workspaceRoot);
|
|
429
|
-
const dshEnv = ctx.shellEnv.collect(exec);
|
|
430
|
-
const request = {
|
|
431
|
-
command: args.command,
|
|
432
|
-
...workdir !== void 0 ? { workdir } : {},
|
|
433
|
-
...args.timeoutMs !== void 0 ? { timeoutMs: args.timeoutMs } : {},
|
|
434
|
-
dshEnv,
|
|
435
|
-
...policy !== void 0 ? { sandboxPolicy: policy } : {}
|
|
436
|
-
};
|
|
437
|
-
if (args.run_in_background === true) {
|
|
438
|
-
if (!backgroundEnabled) throw new Error("run_in_background is disabled for this deployment (enableRunInBackground: false)");
|
|
439
|
-
const jobs = ctx.get("jobs");
|
|
440
|
-
if (jobs === void 0) throw new Error("background jobs unavailable: load @deepseek-ai/dsh-jobs and @deepseek-ai/dsh-tool-jobs");
|
|
441
|
-
if (exec.signal.aborted) {
|
|
442
|
-
const error = new HarnessError("tool call aborted", TOOL_ABORTED);
|
|
443
|
-
error.name = "AbortError";
|
|
444
|
-
throw error;
|
|
673
|
+
if (jobs !== void 0 && promote) {
|
|
674
|
+
const spec = ctx.shell.resolve({
|
|
675
|
+
...request,
|
|
676
|
+
onExpiry: "none"
|
|
677
|
+
});
|
|
678
|
+
let attached;
|
|
679
|
+
try {
|
|
680
|
+
attached = startJob(jobs, args, exec, spec);
|
|
681
|
+
} catch (error) {
|
|
682
|
+
ctx.logger.warn(`bash: job registration refused, running in the foreground with the timeout kill instead: ${String(error)}`);
|
|
683
|
+
}
|
|
684
|
+
if (attached !== void 0) return waitOnJob(jobs, attached, exec, spec);
|
|
445
685
|
}
|
|
686
|
+
const result = await (await ctx.shell.execute(ctx.shell.resolve({
|
|
687
|
+
...request,
|
|
688
|
+
signal: exec.signal
|
|
689
|
+
}))).result();
|
|
690
|
+
if (result.aborted) throw toolAborted();
|
|
446
691
|
return {
|
|
447
|
-
kind: "
|
|
448
|
-
|
|
449
|
-
kind: "bash",
|
|
450
|
-
label: args.command,
|
|
451
|
-
...exec.agent ? { owner: exec.agent } : {},
|
|
452
|
-
run: () => processJob((signal) => ctx.shell.start(ctx.shell.resolve({
|
|
453
|
-
...request,
|
|
454
|
-
signal
|
|
455
|
-
})), (proc) => renderProcessRead(proc.readOutput(), proc.sandbox, escalationModes))
|
|
456
|
-
})
|
|
692
|
+
kind: "foreground",
|
|
693
|
+
...canonicalBashResult(result)
|
|
457
694
|
};
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
695
|
+
},
|
|
696
|
+
presentCall: presentBashCall,
|
|
697
|
+
presentResult: presentBashResult
|
|
698
|
+
});
|
|
699
|
+
};
|
|
700
|
+
if (!backgroundEnabled) {
|
|
701
|
+
ctx.tools.register(bashTool(void 0));
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
let foregroundOnly = ctx.get("jobs") === void 0 ? ctx.tools.register(bashTool(void 0)) : void 0;
|
|
705
|
+
ctx.inject(["jobs"], (jobCtx) => {
|
|
706
|
+
foregroundOnly?.();
|
|
707
|
+
foregroundOnly = void 0;
|
|
708
|
+
const unregister = ctx.tools.register(bashTool(jobCtx.jobs));
|
|
709
|
+
jobCtx.effect(() => () => {
|
|
710
|
+
unregister();
|
|
711
|
+
if (ctx.fiber.state === 2) foregroundOnly = ctx.tools.register(bashTool(void 0));
|
|
712
|
+
});
|
|
713
|
+
});
|
|
476
714
|
}
|
|
477
715
|
//#endregion
|
|
478
716
|
export { Config, apply, inject, name };
|
|
@@ -1,27 +1,50 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Generic-
|
|
2
|
+
* Generic-job adaptation for bash process handles: the terminal outcome the
|
|
3
|
+
* registry records, the pull sources it pumps, and the ring read a foreground
|
|
4
|
+
* call renders when it stops waiting.
|
|
3
5
|
*
|
|
4
6
|
* @module @deepseek-ai/dsh-tool-bash/background
|
|
5
7
|
*/
|
|
8
|
+
import type { SandboxMode } from '@deepseek-ai/dsh-sandbox';
|
|
6
9
|
import type { ShellProcess } from '@deepseek-ai/dsh-shell';
|
|
7
|
-
import type { JobHooks } from '@deepseek-ai/dsh-jobs';
|
|
10
|
+
import type { JobChunk, JobHooks, JobOutcome, JobOutputSource } from '@deepseek-ai/dsh-jobs';
|
|
8
11
|
/**
|
|
9
|
-
* Map a settled background process onto the generic
|
|
12
|
+
* Map a settled background process onto the generic job-outcome vocabulary:
|
|
10
13
|
* `killed` stays `killed` (detail: the signal when one is known), everything
|
|
11
14
|
* else is `completed` with the exit code as detail. A nonzero command exit is
|
|
12
|
-
* reported, not failed, exactly like the foreground rendering.
|
|
15
|
+
* reported, not failed, exactly like the foreground rendering. Sandbox facts
|
|
16
|
+
* join the detail, since a job's terminal reason is the one line every
|
|
17
|
+
* reader — the model's status line, the roster row — shows.
|
|
13
18
|
* @param proc - the settled process handle.
|
|
19
|
+
* @param escalationModes - escalation targets advertised by this composition.
|
|
14
20
|
* @returns the outcome for the `ctx.jobs` registration.
|
|
15
21
|
*/
|
|
16
|
-
export declare function processOutcome(proc: ShellProcess):
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
22
|
+
export declare function processOutcome(proc: ShellProcess, escalationModes?: readonly SandboxMode[]): JobOutcome;
|
|
23
|
+
/**
|
|
24
|
+
* The process's non-consuming stream readers as registry pull sources. They
|
|
25
|
+
* bind lazily because the process is spawned inside the starter, after the
|
|
26
|
+
* registry admitted the job; a read before the spawn yields nothing, and the
|
|
27
|
+
* pump keeps the model's consuming cursor untouched. A rejected spawn's
|
|
28
|
+
* stderr reader carries the provider's `subprocess failed before reporting an
|
|
29
|
+
* outcome: …` note.
|
|
30
|
+
* @param proc - the started process's observed streams, once the starter has spawned it.
|
|
31
|
+
* @returns one source per stream, stdout first.
|
|
32
|
+
*/
|
|
33
|
+
export declare function processSources(proc: () => Pick<ShellProcess, 'observed'> | undefined): JobOutputSource[];
|
|
34
|
+
/**
|
|
35
|
+
* The ring chunks of one consuming registry read as the shell tools render a
|
|
36
|
+
* process read: stdout chunks in order, then every stderr chunk in one
|
|
37
|
+
* `[stderr]` section, so the output a foreground call hands over when it
|
|
38
|
+
* stops waiting reads exactly like the `job_output` reads that follow it.
|
|
39
|
+
* @param chunks - the chunks since the model cursor, in offset order.
|
|
40
|
+
* @returns the delta text, possibly empty.
|
|
41
|
+
*/
|
|
42
|
+
export declare function ringDelta(chunks: readonly JobChunk[]): string;
|
|
20
43
|
/**
|
|
21
44
|
* Adapt asynchronous shell preparation after job admission without exposing a partial process.
|
|
22
45
|
* @param start - starts the process with job-owned cancellation.
|
|
23
|
-
* @param
|
|
46
|
+
* @param outcome - projects the settled process into the job outcome.
|
|
24
47
|
* @returns synchronous job hooks whose completion includes preparation and process settlement.
|
|
25
48
|
*/
|
|
26
|
-
export declare function processJob(start: (signal: AbortSignal) => Promise<ShellProcess>,
|
|
49
|
+
export declare function processJob(start: (signal: AbortSignal) => Promise<ShellProcess>, outcome: (process: ShellProcess) => JobOutcome): JobHooks;
|
|
27
50
|
//# sourceMappingURL=background.d.ts.map
|
package/lib/types/index.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Model-facing Consumer of the `ctx.shell` capability seam.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Model-facing Consumer of the `ctx.shell` capability seam. While a job
|
|
3
|
+
* registry is composed, every call registers its process with `ctx.jobs` as
|
|
4
|
+
* it starts: `run_in_background` returns the id at once, and a foreground call
|
|
5
|
+
* waits on its job until the command finishes or the wait times out, at which
|
|
6
|
+
* point it returns the same id. Without a registry the tool is foreground-only
|
|
7
|
+
* and the executor's deadline kills the command.
|
|
5
8
|
*
|
|
6
9
|
* TODO(permissions): deployment policy belongs in `tools/pre-execute` and
|
|
7
10
|
* sandboxing executors; see docs/architecture.md § Where new behavior goes.
|
|
@@ -13,8 +16,21 @@ export declare const name = "tool-bash";
|
|
|
13
16
|
export declare const inject: string[];
|
|
14
17
|
/** Configuration for the bash tool. */
|
|
15
18
|
export interface Config {
|
|
16
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* Expose `run_in_background` while a job registry is composed (default
|
|
21
|
+
* true); disabled calls are also rejected. Without a registry the tool is
|
|
22
|
+
* foreground-only regardless.
|
|
23
|
+
*/
|
|
17
24
|
enableRunInBackground?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Keep a foreground command that reaches its timeout running as a
|
|
27
|
+
* background job instead of killing it (default true). Applies only while
|
|
28
|
+
* background execution is available: with `enableRunInBackground` false or
|
|
29
|
+
* no job registry, the executor's deadline kills the command. A foreground
|
|
30
|
+
* command the registry refuses at its start (admission or a missing
|
|
31
|
+
* controller) also runs under the deadline kill.
|
|
32
|
+
*/
|
|
33
|
+
promoteOnTimeout?: boolean;
|
|
18
34
|
}
|
|
19
35
|
/** Runtime configuration schema for the bash tool plugin. */
|
|
20
36
|
export declare const Config: z<Config>;
|
package/lib/types/render.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module @deepseek-ai/dsh-tool-bash/render
|
|
5
5
|
*/
|
|
6
|
-
import type {
|
|
6
|
+
import type { ShellRunResult, ShellSandboxInfo } from '@deepseek-ai/dsh-shell';
|
|
7
7
|
import type { SandboxMode } from '@deepseek-ai/dsh-sandbox';
|
|
8
8
|
/**
|
|
9
9
|
* Shape one finished run into the text the model sees: stdout, then a marked
|
|
@@ -14,20 +14,39 @@ import type { SandboxMode } from '@deepseek-ai/dsh-sandbox';
|
|
|
14
14
|
* @param escalationModes - the escalation targets this composition advertises;
|
|
15
15
|
* non-empty adds the same-turn escalation hint after a denial marker
|
|
16
16
|
* (default `[]`: no hint).
|
|
17
|
-
* @returns the model-facing text: output body (or `(no output)`), then any timeout/signal/exit markers, each on its own line.
|
|
17
|
+
* @returns the model-facing text: output body (or `(no output)`), then any timeout/stopped/signal/exit markers, each on its own line.
|
|
18
18
|
*/
|
|
19
|
-
export declare function renderResult(result: ShellRunResult
|
|
19
|
+
export declare function renderResult(result: ShellRunResult & {
|
|
20
|
+
stopped?: string;
|
|
21
|
+
}, escalationModes?: readonly SandboxMode[]): string;
|
|
20
22
|
/**
|
|
21
|
-
* Shape
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* @param
|
|
23
|
+
* Shape a foreground call that stopped waiting into the text the model sees:
|
|
24
|
+
* the output captured so far (one consuming registry read taken at that
|
|
25
|
+
* point, so `job_output` continues exactly after it), then the still-running
|
|
26
|
+
* marker and the job hand-off guidance.
|
|
27
|
+
* @param promoted - the promoted result value: the job id, the wait that
|
|
28
|
+
* expired, and the output so far.
|
|
29
|
+
* @returns the model-facing text for a promoted call.
|
|
30
|
+
*/
|
|
31
|
+
export declare function renderPromoted(promoted: {
|
|
32
|
+
jobId: string;
|
|
33
|
+
timeoutMs: number;
|
|
34
|
+
output: string;
|
|
35
|
+
}): string;
|
|
36
|
+
/**
|
|
37
|
+
* Shape the one consuming registry read a foreground call embeds in its
|
|
38
|
+
* result when it stops waiting: the output produced so far, plus the
|
|
39
|
+
* dropped-output notice (naming the job's spill files) when the model cursor
|
|
40
|
+
* fell behind the ring, and the sandbox notices. Later `job_output` reads
|
|
41
|
+
* render the same ring through the job tools.
|
|
42
|
+
* @param delta - the read's chunks as rendered text.
|
|
43
|
+
* @param lossy - whether bytes before the delta were evicted unread.
|
|
44
|
+
* @param spillPaths - the complete-stream files the job currently advertises.
|
|
26
45
|
* @param sandbox - settled sandbox facts, when this was a confined process.
|
|
27
46
|
* @param escalationModes - escalation targets advertised by this composition.
|
|
28
47
|
* @returns the delta text with any loss or sandbox notice appended.
|
|
29
48
|
*/
|
|
30
|
-
export declare function
|
|
49
|
+
export declare function renderJobRead(delta: string, lossy: boolean, spillPaths: readonly string[], sandbox?: ShellSandboxInfo, escalationModes?: readonly SandboxMode[]): string;
|
|
31
50
|
/**
|
|
32
51
|
* The exit-status parse is the shared marker-contract half of the shell-tool
|
|
33
52
|
* rendering story, owned by `@deepseek-ai/dsh-shell` so `dsh-tool-pwsh` reuses
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-tool-bash",
|
|
3
3
|
"description": "Model-facing bash tool with optional generic background-job and sandbox-escalation support",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.7-alpha.1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -27,41 +27,41 @@
|
|
|
27
27
|
],
|
|
28
28
|
"license": "MIT",
|
|
29
29
|
"peerDependencies": {
|
|
30
|
-
"@deepseek-ai/dsh-
|
|
31
|
-
"@deepseek-ai/dsh-
|
|
32
|
-
"@deepseek-ai/dsh-shell-env": "^0.1.
|
|
33
|
-
"@deepseek-ai/dsh-llm": "^0.1.
|
|
34
|
-
"@deepseek-ai/dsh-sandbox": "^0.1.
|
|
35
|
-
"@deepseek-ai/dsh-
|
|
36
|
-
"@deepseek-ai/dsh-
|
|
37
|
-
"@deepseek-ai/dsh-jobs": "^0.1.
|
|
38
|
-
"@deepseek-ai/dsh-
|
|
39
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
40
|
-
"@deepseek-ai/dsh-
|
|
30
|
+
"@deepseek-ai/dsh-shell": "^0.1.7-alpha.1",
|
|
31
|
+
"@deepseek-ai/dsh-agent": "^0.1.7-alpha.1",
|
|
32
|
+
"@deepseek-ai/dsh-shell-env": "^0.1.7-alpha.1",
|
|
33
|
+
"@deepseek-ai/dsh-llm": "^0.1.7-alpha.1",
|
|
34
|
+
"@deepseek-ai/dsh-sandbox-policy": "^0.1.7-alpha.1",
|
|
35
|
+
"@deepseek-ai/dsh-system-prompt": "^0.1.7-alpha.1",
|
|
36
|
+
"@deepseek-ai/dsh-sandbox": "^0.1.7-alpha.1",
|
|
37
|
+
"@deepseek-ai/dsh-jobs": "^0.1.7-alpha.1",
|
|
38
|
+
"@deepseek-ai/dsh-user-approval": "^0.1.7-alpha.1",
|
|
39
|
+
"@deepseek-ai/cordis": "^4.0.3",
|
|
40
|
+
"@deepseek-ai/dsh-tools": "^0.1.7-alpha.1"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@deepseek-ai/schemastery": "^3.18.
|
|
43
|
+
"@deepseek-ai/schemastery": "^3.18.3"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@deepseek-ai/dsh-agent": "^0.1.
|
|
47
|
-
"@deepseek-ai/dsh-agent-loop": "^0.1.
|
|
48
|
-
"@deepseek-ai/dsh-agent-loop-testkit": "^0.1.
|
|
49
|
-
"@deepseek-ai/dsh-shell": "^0.1.
|
|
50
|
-
"@deepseek-ai/dsh-shell-env": "^0.1.
|
|
51
|
-
"@deepseek-ai/dsh-bash-local": "^0.1.
|
|
52
|
-
"@deepseek-ai/dsh-llm": "^0.1.
|
|
53
|
-
"@deepseek-ai/dsh-
|
|
54
|
-
"@deepseek-ai/dsh-
|
|
55
|
-
"@deepseek-ai/dsh-sandbox-policy": "^0.1.
|
|
56
|
-
"@deepseek-ai/dsh-session
|
|
57
|
-
"@deepseek-ai/dsh-
|
|
58
|
-
"@deepseek-ai/dsh-
|
|
59
|
-
"@deepseek-ai/dsh-
|
|
60
|
-
"@deepseek-ai/dsh-
|
|
61
|
-
"@deepseek-ai/dsh-
|
|
62
|
-
"@deepseek-ai/
|
|
63
|
-
"@deepseek-ai/dsh-
|
|
64
|
-
"@deepseek-ai/
|
|
65
|
-
"@deepseek-ai/dsh-
|
|
46
|
+
"@deepseek-ai/dsh-agent": "^0.1.7-alpha.1",
|
|
47
|
+
"@deepseek-ai/dsh-agent-loop": "^0.1.7-alpha.1",
|
|
48
|
+
"@deepseek-ai/dsh-agent-loop-testkit": "^0.1.7-alpha.1",
|
|
49
|
+
"@deepseek-ai/dsh-shell": "^0.1.7-alpha.1",
|
|
50
|
+
"@deepseek-ai/dsh-shell-env": "^0.1.7-alpha.1",
|
|
51
|
+
"@deepseek-ai/dsh-bash-local": "^0.1.7-alpha.1",
|
|
52
|
+
"@deepseek-ai/dsh-llm": "^0.1.7-alpha.1",
|
|
53
|
+
"@deepseek-ai/dsh-subprocess-local": "^0.1.7-alpha.1",
|
|
54
|
+
"@deepseek-ai/dsh-sandbox": "^0.1.7-alpha.1",
|
|
55
|
+
"@deepseek-ai/dsh-sandbox-policy": "^0.1.7-alpha.1",
|
|
56
|
+
"@deepseek-ai/dsh-session": "^0.1.7-alpha.1",
|
|
57
|
+
"@deepseek-ai/dsh-session-persistence-jsonl": "^0.1.7-alpha.1",
|
|
58
|
+
"@deepseek-ai/dsh-system-prompt": "^0.1.7-alpha.1",
|
|
59
|
+
"@deepseek-ai/dsh-jobs-local": "^0.1.7-alpha.1",
|
|
60
|
+
"@deepseek-ai/dsh-tool-jobs": "^0.1.7-alpha.1",
|
|
61
|
+
"@deepseek-ai/dsh-jobs": "^0.1.7-alpha.1",
|
|
62
|
+
"@deepseek-ai/dsh-tools": "^0.1.7-alpha.1",
|
|
63
|
+
"@deepseek-ai/dsh-user-approval": "^0.1.7-alpha.1",
|
|
64
|
+
"@deepseek-ai/cordis": "^4.0.3",
|
|
65
|
+
"@deepseek-ai/dsh-session-projection": "^0.1.7-alpha.1"
|
|
66
66
|
}
|
|
67
67
|
}
|