@deepseek-ai/dsh-tool-terminal 0.0.1-rc.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +28 -0
- package/README.i18n.yaml +6 -0
- package/README.md +71 -0
- package/README.zh.md +71 -0
- package/lib/index.js +610 -0
- package/lib/invariant.js +23 -0
- package/lib/types/index.d.ts +32 -0
- package/lib/types/invariant.d.ts +16 -0
- package/lib/types/render.d.ts +81 -0
- package/package.json +68 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, DeepSeek
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.i18n.yaml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
|
2
|
+
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
|
+
# after editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write packages/terminal/tool-terminal/README.md
|
|
5
|
+
README.md: f6fd4020d423425d7b20ad6bc21b4e009cf25c85
|
|
6
|
+
README.zh.md: 827d179515779f87665a80920e72a11be051023f
|
package/README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# @deepseek-ai/dsh-tool-terminal
|
|
2
|
+
|
|
3
|
+
English | [中文](README.zh.md)
|
|
4
|
+
|
|
5
|
+
Six model-facing tools over `ctx.terminals`: `terminal_open`, `terminal_send`, `terminal_read`, `terminal_signal`, `terminal_close`, and `terminal_list`. Every operation requires the exact initiating `Agent`, so a model cannot address another agent's terminal even if it learns the id.
|
|
6
|
+
|
|
7
|
+
`terminal_send(run_in_background: true)` reuses `ctx.jobs`; job preflight and the PTY service's exclusive per-session send reservation occur before the job id is returned, completion is collected with `job_output`, and `job_kill` delivers `SIGINT` to the foreground process group. Foreground sends use terminal call/result cards. Background sends use a generic execute card; open, read, signal, close, and list use generic `execute`, `read`, `execute`, `delete`, and `read` cards respectively. None declares source locations.
|
|
8
|
+
|
|
9
|
+
## Config
|
|
10
|
+
|
|
11
|
+
| key | default | meaning |
|
|
12
|
+
|---|---:|---|
|
|
13
|
+
| `enableRunInBackground` | `true` | expose and accept `run_in_background`; false omits the schema field and rejects a forced undeclared argument |
|
|
14
|
+
| `maxResultBytes` | `262144` | UTF-8 cap (minimum `64`) for each complete terminal result or PTY job output after wait, session, pagination, truncation, and task-status metadata |
|
|
15
|
+
|
|
16
|
+
Both values are validated at load. The minimum result cap keeps every registry-issued session or job id visible in its creation acknowledgement. When a result exceeds `maxResultBytes`, rendering reserves space for control metadata and a truncation marker when they fit; cuts preserve UTF-8 boundaries. Each terminal definition's final-content callback applies the same cap after normalized pre-, around-, and post-execute policy failures, denials, short-circuits, replacements, or blocks; a structured multi-block policy result retains its shape.
|
|
17
|
+
|
|
18
|
+
## Model Experience
|
|
19
|
+
|
|
20
|
+
### System prompt
|
|
21
|
+
|
|
22
|
+
#### What the model sees
|
|
23
|
+
|
|
24
|
+
The plugin contributes this fixed guidance section:
|
|
25
|
+
|
|
26
|
+
##### Terminal guidance
|
|
27
|
+
|
|
28
|
+
```markdown
|
|
29
|
+
Use a terminal session only when work needs persistent terminal state or interactive stdin; prefer shell/read/write/edit for bounded one-shot operations. Track every terminal session id and close sessions that no longer matter. An inferred_idle or timeout result does not prove the foreground command exited.
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
#### Token effect
|
|
33
|
+
|
|
34
|
+
Small fixed input cost on every request while the plugin is active.
|
|
35
|
+
|
|
36
|
+
#### KV Cache effect
|
|
37
|
+
|
|
38
|
+
Prefix-stable while the registration scope and guidance text are unchanged.
|
|
39
|
+
|
|
40
|
+
### Tool schemas
|
|
41
|
+
|
|
42
|
+
#### What the model sees
|
|
43
|
+
|
|
44
|
+
The six generated schemas are listed in the [`dsh-tool-terminal` catalog section](../../../docs/tool-catalog.md#deepseek-aidsh-tool-terminal). Their fixed schema tokens are present whenever this plugin is active; agent-scoped tool filtering may hide them.
|
|
45
|
+
|
|
46
|
+
#### Token effect
|
|
47
|
+
|
|
48
|
+
Fixed schema cost on requests where the tools are visible.
|
|
49
|
+
|
|
50
|
+
#### KV Cache effect
|
|
51
|
+
|
|
52
|
+
Prefix-stable while tool visibility and definitions are unchanged.
|
|
53
|
+
|
|
54
|
+
### Tool results and task context
|
|
55
|
+
|
|
56
|
+
#### What the model sees
|
|
57
|
+
|
|
58
|
+
Spawn returns the id and bounded MOTD. Send/read return bounded terminal text plus readiness/history markers. Background mode returns a generic job id. Every terminal-owned or policy-produced single-text result is capped by `maxResultBytes` after normalized tool or pipeline errors, denials, short-circuits, replacements, blocks, and generic job status text. Structured multi-block policy results retain their shape. Results remain in session history until compaction; incremental task reads do not repeat consumed output. Programmatic callers receive typed session snapshots, bounded provider read/send DTOs, signal and close outcomes, or `{ kind: "background", jobId }`; Native rendering applies the presentation cap above.
|
|
59
|
+
|
|
60
|
+
#### Token effect
|
|
61
|
+
|
|
62
|
+
Terminal-owned and policy-produced single-text results are data-dependent and bounded by `maxResultBytes`; a policy that deliberately substitutes structured multi-block content owns that content's bound. Each returned result remains in history until compaction.
|
|
63
|
+
|
|
64
|
+
#### KV Cache effect
|
|
65
|
+
|
|
66
|
+
Append-only; new results follow the reusable request prefix.
|
|
67
|
+
|
|
68
|
+
## Known Limitations and Deferred Work
|
|
69
|
+
|
|
70
|
+
- No named key sequence, TUI, BEL, resize, auto-start, or cross-agent sharing schema is exposed.
|
|
71
|
+
- Background mode requires both `@deepseek-ai/dsh-jobs` and its model-facing controller.
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# @deepseek-ai/dsh-tool-terminal
|
|
2
|
+
|
|
3
|
+
[English](README.md) | 中文
|
|
4
|
+
|
|
5
|
+
基于 `ctx.terminals` 提供 6 个面向模型的工具:`terminal_open`、`terminal_send`、`terminal_read`、`terminal_signal`、`terminal_close` 和 `terminal_list`。每项操作都要求提供完全相同的发起 `Agent`,因此即使模型获知另一个 agent(智能体)的 id,也无法操作其终端。
|
|
6
|
+
|
|
7
|
+
`terminal_send(run_in_background: true)` 会复用 `ctx.jobs`;任务预检和 PTY 服务对每个会话的独占发送预留都发生在返回 job id 之前。系统通过 `job_output` 收集完成结果,`job_kill` 则向前台进程组发送 `SIGINT`。前台发送使用终端调用/结果卡片。后台发送使用通用执行卡片;打开、读取、发送信号、关闭和列出操作则分别使用通用 `execute`、`read`、`execute`、`delete` 和 `read` 卡片。所有操作都不声明源位置。
|
|
8
|
+
|
|
9
|
+
## 配置
|
|
10
|
+
|
|
11
|
+
| 键 | 默认值 | 含义 |
|
|
12
|
+
|---|---:|---|
|
|
13
|
+
| `enableRunInBackground` | `true` | 公开并接受 `run_in_background`;设为 false 时,schema 会省略该字段,并拒绝强行传入未声明的参数 |
|
|
14
|
+
| `maxResultBytes` | `262144` | 每个完整终端结果或 PTY 任务输出的 UTF-8 上限(最小值 `64`);在等待、会话、分页、截断和任务状态元数据全部加入后计算 |
|
|
15
|
+
|
|
16
|
+
两个值都会在加载时验证。最小结果上限可保证注册表签发的每个会话或 job id 都能出现在创建确认中。结果超过 `maxResultBytes` 时,只要空间允许,渲染会为控制元数据和截断标记预留空间;截断会保留 UTF-8 边界。每个终端定义的最终内容回调都会应用同一个上限,涵盖经过规范化的 pre-execute、around-execute 与 post-execute 策略失败、拒绝、短路、替换或阻止;结构化的多块策略结果保留其结构。
|
|
17
|
+
|
|
18
|
+
## 模型体验
|
|
19
|
+
|
|
20
|
+
### 系统提示词
|
|
21
|
+
|
|
22
|
+
#### 模型看到的内容
|
|
23
|
+
|
|
24
|
+
该插件贡献以下固定指引章节:
|
|
25
|
+
|
|
26
|
+
##### 终端指引
|
|
27
|
+
|
|
28
|
+
```markdown
|
|
29
|
+
Use a terminal session only when work needs persistent terminal state or interactive stdin; prefer shell/read/write/edit for bounded one-shot operations. Track every terminal session id and close sessions that no longer matter. An inferred_idle or timeout result does not prove the foreground command exited.
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
#### Token 影响
|
|
33
|
+
|
|
34
|
+
插件活跃期间,每次请求都会产生少量固定输入成本。
|
|
35
|
+
|
|
36
|
+
#### KV Cache 影响
|
|
37
|
+
|
|
38
|
+
注册范围和指引文本不变时,前缀保持稳定。
|
|
39
|
+
|
|
40
|
+
### 工具 schema
|
|
41
|
+
|
|
42
|
+
#### 模型看到的内容
|
|
43
|
+
|
|
44
|
+
6 个生成的 schema 列在 [`dsh-tool-terminal` 目录章节](../../../docs/tool-catalog.md#deepseek-aidsh-tool-terminal)中。此插件活跃时,请求中会包含它们的固定 schema token;按 agent 范围过滤工具时可能隐藏这些 schema。
|
|
45
|
+
|
|
46
|
+
#### Token 影响
|
|
47
|
+
|
|
48
|
+
工具可见的请求会产生固定的 schema 成本。
|
|
49
|
+
|
|
50
|
+
#### KV Cache 影响
|
|
51
|
+
|
|
52
|
+
工具可见性与定义不变时,前缀保持稳定。
|
|
53
|
+
|
|
54
|
+
### 工具结果与任务上下文
|
|
55
|
+
|
|
56
|
+
#### 模型看到的内容
|
|
57
|
+
|
|
58
|
+
spawn 会返回 id 和有界 MOTD。发送/读取会返回有界终端文本以及就绪/历史标记。后台模式返回通用 job id。所有终端自身或策略产生的单文本结果,在经过规范化的工具或流水线错误、拒绝、短路、替换、阻止与通用任务状态文本之后,都受 `maxResultBytes` 限制。结构化的多块策略结果保留其结构。结果会保留在会话历史中直到压缩(compaction);增量任务读取不会重复已经消费的输出。编程调用方会收到带类型的会话快照、有界的提供方读取/发送 DTO、信号与关闭结果,或 `{ kind: "background", jobId }`;Native 渲染会应用上述呈现上限。
|
|
59
|
+
|
|
60
|
+
#### Token 影响
|
|
61
|
+
|
|
62
|
+
终端自身与策略产生的单文本结果随数据变化,并受 `maxResultBytes` 限制;如果策略有意替换为结构化多块内容,则由该策略负责限制内容。每个返回结果都会保留在历史中直到压缩。
|
|
63
|
+
|
|
64
|
+
#### KV Cache 影响
|
|
65
|
+
|
|
66
|
+
仅追加;新结果位于可复用请求前缀之后。
|
|
67
|
+
|
|
68
|
+
## 已知限制与暂缓事项
|
|
69
|
+
|
|
70
|
+
- 不公开具名按键序列、TUI、BEL、调整大小、自动启动或跨 agent 共享 schema。
|
|
71
|
+
- 后台模式同时依赖 `@deepseek-ai/dsh-jobs` 及其面向模型的控制器。
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
import z from "@deepseek-ai/schemastery";
|
|
2
|
+
import { TerminalSessionId } from "@deepseek-ai/dsh-terminal";
|
|
3
|
+
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
4
|
+
import { TextRetainer } from "@deepseek-ai/dsh-output-retention";
|
|
5
|
+
//#region lib/types/render.js
|
|
6
|
+
/** Model and UI rendering for persistent terminal tool results. */
|
|
7
|
+
const encoder = new TextEncoder();
|
|
8
|
+
const TRUNCATED = "\n[output truncated]";
|
|
9
|
+
function byteLength(text) {
|
|
10
|
+
return encoder.encode(text).byteLength;
|
|
11
|
+
}
|
|
12
|
+
function retain(text, maxBytes, kind) {
|
|
13
|
+
const retainer = new TextRetainer({
|
|
14
|
+
kind,
|
|
15
|
+
maxBytes
|
|
16
|
+
});
|
|
17
|
+
retainer.push(text);
|
|
18
|
+
return retainer.finish().text;
|
|
19
|
+
}
|
|
20
|
+
function fitWithSuffix(content, suffix, maxBytes) {
|
|
21
|
+
const fixedBytes = byteLength(suffix);
|
|
22
|
+
if (fixedBytes >= maxBytes) return retain(suffix, maxBytes, "tail");
|
|
23
|
+
return `${retain(content, maxBytes - fixedBytes, "tail")}${suffix}`;
|
|
24
|
+
}
|
|
25
|
+
function fitWithPrefix(prefix, content, maxBytes) {
|
|
26
|
+
const fixed = `${prefix}${TRUNCATED}`;
|
|
27
|
+
const fixedBytes = byteLength(fixed);
|
|
28
|
+
if (fixedBytes >= maxBytes) return retain(fixed, maxBytes, "head");
|
|
29
|
+
return `${prefix}${retain(content, maxBytes - fixedBytes, "tail")}${TRUNCATED}`;
|
|
30
|
+
}
|
|
31
|
+
function boundBodyWithSuffix(content, metadata, upstreamTruncated, maxBytes) {
|
|
32
|
+
const complete = `${content}${`${metadata}${upstreamTruncated ? TRUNCATED : ""}`}`;
|
|
33
|
+
if (byteLength(complete) <= maxBytes) return complete;
|
|
34
|
+
return fitWithSuffix(content, `${metadata}${TRUNCATED}`, maxBytes);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Bound one complete terminal acknowledgement while preserving UTF-8 cuts.
|
|
38
|
+
* @param text - complete acknowledgement text.
|
|
39
|
+
* @param maxBytes - positive final result cap.
|
|
40
|
+
* @returns bounded text with a truncation marker when it fits.
|
|
41
|
+
*/
|
|
42
|
+
function boundTerminalText(text, maxBytes) {
|
|
43
|
+
if (byteLength(text) <= maxBytes) return text;
|
|
44
|
+
const markerBytes = byteLength(TRUNCATED);
|
|
45
|
+
if (markerBytes >= maxBytes) return retain(TRUNCATED, maxBytes, "tail");
|
|
46
|
+
return `${retain(text, maxBytes - markerBytes, "head")}${TRUNCATED}`;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Render one created session and its bounded MOTD.
|
|
50
|
+
* @param result - published spawn result.
|
|
51
|
+
* @param maxBytes - complete UTF-8 result cap.
|
|
52
|
+
* @returns Model-facing session acknowledgement.
|
|
53
|
+
*/
|
|
54
|
+
function renderSpawn(result, maxBytes) {
|
|
55
|
+
const prefix = `started terminal session ${result.name === void 0 ? result.sessionId : `${result.sessionId} (${result.name})`} [type: ${result.type}]\n`;
|
|
56
|
+
const motd = result.motd || "(no startup output)";
|
|
57
|
+
const complete = `${prefix}${motd}`;
|
|
58
|
+
return byteLength(complete) <= maxBytes ? complete : fitWithPrefix(prefix, motd, maxBytes);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Render one settled interactive send.
|
|
62
|
+
* @param result - settled send outcome.
|
|
63
|
+
* @param maxBytes - complete UTF-8 result cap.
|
|
64
|
+
* @returns Terminal output plus wait/session markers.
|
|
65
|
+
*/
|
|
66
|
+
function renderSend(result, maxBytes) {
|
|
67
|
+
const output = result.viewport || "(no new output)";
|
|
68
|
+
const status = result.sessionStatus.kind === "running" ? "running" : `exited code=${result.sessionStatus.exitCode ?? "null"} signal=${result.sessionStatus.signal ?? "null"}`;
|
|
69
|
+
return boundBodyWithSuffix(output, `\n[wait: ${result.waitReason}]\n[session: ${status}]`, result.truncated, maxBytes);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Render one incremental background operation read.
|
|
73
|
+
* @param read - consuming operation delta.
|
|
74
|
+
* @returns Delta plus its upstream truncation marker. The generic task control
|
|
75
|
+
* applies the producer's complete-result cap after adding job status.
|
|
76
|
+
*/
|
|
77
|
+
function renderSendRead(read) {
|
|
78
|
+
const separator = read.delta.endsWith("\n") || read.delta.length === 0 ? "" : "\n";
|
|
79
|
+
return `${read.delta}${read.truncated ? `${separator}[output truncated]` : ""}`;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Render one bounded historical page.
|
|
83
|
+
* @param result - retained scrollback page.
|
|
84
|
+
* @param maxBytes - complete UTF-8 result cap.
|
|
85
|
+
* @returns Page text plus pagination and truncation markers.
|
|
86
|
+
*/
|
|
87
|
+
function renderRead(result, maxBytes) {
|
|
88
|
+
return boundBodyWithSuffix(result.text || "(no retained output)", `\n[lines: ${result.lineBegin}-${result.lineEnd} of ${result.totalLines}]`, result.truncated, maxBytes);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Render owner-visible live sessions.
|
|
92
|
+
* @param sessions - fresh owner-scoped snapshots.
|
|
93
|
+
* @param maxBytes - complete UTF-8 result cap.
|
|
94
|
+
* @returns One line per session or the empty marker.
|
|
95
|
+
*/
|
|
96
|
+
function renderList(sessions, maxBytes) {
|
|
97
|
+
if (sessions.length === 0) return "(no terminal sessions)";
|
|
98
|
+
return boundBodyWithSuffix(sessions.map((session) => {
|
|
99
|
+
const name = session.name === void 0 ? "" : ` (${session.name})`;
|
|
100
|
+
const pid = session.pid === void 0 ? "" : ` pid=${session.pid}`;
|
|
101
|
+
const status = session.status.kind === "running" ? "running" : `exited code=${session.status.exitCode ?? "null"} signal=${session.status.signal ?? "null"}`;
|
|
102
|
+
return `${session.sessionId}${name} [${session.type}] ${status}${pid}`;
|
|
103
|
+
}).join("\n"), "", false, maxBytes);
|
|
104
|
+
}
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region lib/types/index.js
|
|
107
|
+
/**
|
|
108
|
+
* Six model-facing persistent terminal tools. Owner identity comes from the exact
|
|
109
|
+
* tool execution Agent; generic `ctx.jobs` owns background ids and collection.
|
|
110
|
+
* @module @deepseek-ai/dsh-tool-terminal
|
|
111
|
+
*/
|
|
112
|
+
/** Cordis plugin name. */
|
|
113
|
+
const name = "tool-terminal";
|
|
114
|
+
/** Required capability, registry, and prompt services. */
|
|
115
|
+
const inject = [
|
|
116
|
+
"terminals",
|
|
117
|
+
"tools",
|
|
118
|
+
"systemPrompt"
|
|
119
|
+
];
|
|
120
|
+
/** Default cap for one complete model-facing terminal result. */
|
|
121
|
+
const DEFAULT_MAX_RESULT_BYTES = 256 * 1024;
|
|
122
|
+
/** Smallest cap that preserves every counter-backed PTY and job id in its creation acknowledgement. */
|
|
123
|
+
const MIN_MAX_RESULT_BYTES = 64;
|
|
124
|
+
/** Schemastery configuration for the terminal tool consumer. */
|
|
125
|
+
const Config = z.object({
|
|
126
|
+
enableRunInBackground: z.boolean().default(true),
|
|
127
|
+
maxResultBytes: z.number().step(1).min(64).max(Number.MAX_SAFE_INTEGER).default(DEFAULT_MAX_RESULT_BYTES)
|
|
128
|
+
});
|
|
129
|
+
const SESSION_STATUS_SCHEMA = { oneOf: [{
|
|
130
|
+
type: "object",
|
|
131
|
+
additionalProperties: false,
|
|
132
|
+
properties: { kind: {
|
|
133
|
+
type: "string",
|
|
134
|
+
required: true,
|
|
135
|
+
const: "running"
|
|
136
|
+
} }
|
|
137
|
+
}, {
|
|
138
|
+
type: "object",
|
|
139
|
+
additionalProperties: false,
|
|
140
|
+
properties: {
|
|
141
|
+
kind: {
|
|
142
|
+
type: "string",
|
|
143
|
+
required: true,
|
|
144
|
+
const: "exited"
|
|
145
|
+
},
|
|
146
|
+
exitCode: {
|
|
147
|
+
required: true,
|
|
148
|
+
oneOf: [{ type: "integer" }, { type: "null" }]
|
|
149
|
+
},
|
|
150
|
+
signal: {
|
|
151
|
+
required: true,
|
|
152
|
+
oneOf: [{ type: "string" }, { type: "null" }]
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}] };
|
|
156
|
+
const SESSION_SNAPSHOT_PROPERTIES = {
|
|
157
|
+
sessionId: {
|
|
158
|
+
type: "string",
|
|
159
|
+
required: true
|
|
160
|
+
},
|
|
161
|
+
name: { type: "string" },
|
|
162
|
+
type: {
|
|
163
|
+
type: "string",
|
|
164
|
+
required: true
|
|
165
|
+
},
|
|
166
|
+
pid: { type: "integer" },
|
|
167
|
+
status: {
|
|
168
|
+
...SESSION_STATUS_SCHEMA,
|
|
169
|
+
required: true
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
const SESSION_SNAPSHOT_SCHEMA = {
|
|
173
|
+
type: "object",
|
|
174
|
+
additionalProperties: false,
|
|
175
|
+
properties: SESSION_SNAPSHOT_PROPERTIES
|
|
176
|
+
};
|
|
177
|
+
const BACKGROUND_TASK_OUTPUT_SCHEMA = {
|
|
178
|
+
type: "object",
|
|
179
|
+
additionalProperties: false,
|
|
180
|
+
properties: {
|
|
181
|
+
kind: {
|
|
182
|
+
type: "string",
|
|
183
|
+
required: true,
|
|
184
|
+
const: "background"
|
|
185
|
+
},
|
|
186
|
+
jobId: {
|
|
187
|
+
type: "string",
|
|
188
|
+
required: true
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
function requireAgent(agent) {
|
|
193
|
+
if (agent === void 0) throw new Error("terminal tools require an initiating agent");
|
|
194
|
+
return agent;
|
|
195
|
+
}
|
|
196
|
+
function sessionId(args) {
|
|
197
|
+
if (args.sessionId.length === 0) throw new Error("sessionId must be a non-empty string");
|
|
198
|
+
return TerminalSessionId(args.sessionId);
|
|
199
|
+
}
|
|
200
|
+
function textResult(text, maxBytes) {
|
|
201
|
+
return [{
|
|
202
|
+
type: "text",
|
|
203
|
+
text: boundTerminalText(text, maxBytes)
|
|
204
|
+
}];
|
|
205
|
+
}
|
|
206
|
+
function rawContentText(content) {
|
|
207
|
+
if (content.length !== 1) return void 0;
|
|
208
|
+
const block = content[0];
|
|
209
|
+
return block?.type === "text" ? block.text : void 0;
|
|
210
|
+
}
|
|
211
|
+
function sendDetail(result) {
|
|
212
|
+
return result.sessionStatus.kind === "running" ? `wait: ${result.waitReason}` : `session exited: ${result.sessionStatus.exitCode ?? result.sessionStatus.signal ?? "unknown"}`;
|
|
213
|
+
}
|
|
214
|
+
/** Register all terminal tools and the minimal usage guidance. */
|
|
215
|
+
function apply(ctx, config = {}) {
|
|
216
|
+
const enableRunInBackground = config.enableRunInBackground ?? true;
|
|
217
|
+
const maxResultBytes = config.maxResultBytes ?? 262144;
|
|
218
|
+
if (!Number.isSafeInteger(maxResultBytes) || maxResultBytes < 64) throw new Error(`tool-terminal: maxResultBytes must be a safe integer of at least 64`);
|
|
219
|
+
const finalizeContent = (_exec, result) => {
|
|
220
|
+
const raw = rawContentText(result.content);
|
|
221
|
+
return raw === void 0 ? void 0 : textResult(raw, maxResultBytes);
|
|
222
|
+
};
|
|
223
|
+
ctx.systemPrompt.section({
|
|
224
|
+
name: "tool:pty",
|
|
225
|
+
order: 106,
|
|
226
|
+
text: "Use a terminal session only when work needs persistent terminal state or interactive stdin; prefer shell/read/write/edit for bounded one-shot operations. Track every terminal session id and close sessions that no longer matter. An inferred_idle or timeout result does not prove the foreground command exited."
|
|
227
|
+
});
|
|
228
|
+
ctx.tools.register(defineTool({
|
|
229
|
+
name: "terminal_open",
|
|
230
|
+
description: "Create a persistent, owner-isolated terminal session from a registered backend type. Use this for shell or REPL state that must survive across tool calls.",
|
|
231
|
+
parameters: {
|
|
232
|
+
type: {
|
|
233
|
+
type: "string",
|
|
234
|
+
required: true,
|
|
235
|
+
description: "Registered terminal backend type, usually \"shell\"."
|
|
236
|
+
},
|
|
237
|
+
name: {
|
|
238
|
+
type: "string",
|
|
239
|
+
description: "Optional owner-local display name such as \"main\" or \"gdb\"."
|
|
240
|
+
},
|
|
241
|
+
cwd: {
|
|
242
|
+
type: "string",
|
|
243
|
+
description: "Initial working directory. Defaults to the deployment workspace root."
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
finalizeContent,
|
|
247
|
+
output: {
|
|
248
|
+
schema: {
|
|
249
|
+
type: "object",
|
|
250
|
+
additionalProperties: false,
|
|
251
|
+
properties: {
|
|
252
|
+
...SESSION_SNAPSHOT_PROPERTIES,
|
|
253
|
+
motd: {
|
|
254
|
+
type: "string",
|
|
255
|
+
required: true
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
render: (_args, value) => [{
|
|
260
|
+
type: "text",
|
|
261
|
+
text: renderSpawn(value, maxResultBytes)
|
|
262
|
+
}]
|
|
263
|
+
},
|
|
264
|
+
async execute(args, exec) {
|
|
265
|
+
if (args.type.length === 0) throw new Error("type must be a non-empty string");
|
|
266
|
+
return await ctx.terminals.spawn(requireAgent(exec.agent), {
|
|
267
|
+
type: args.type,
|
|
268
|
+
...args.name !== void 0 ? { name: args.name } : {},
|
|
269
|
+
...args.cwd !== void 0 ? { cwd: args.cwd } : {}
|
|
270
|
+
}, exec.signal);
|
|
271
|
+
},
|
|
272
|
+
presentCall: (args) => {
|
|
273
|
+
const parsed = args;
|
|
274
|
+
return {
|
|
275
|
+
card: "generic",
|
|
276
|
+
title: `Open terminal ${parsed.name ?? parsed.type}`,
|
|
277
|
+
kind: "execute"
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
}));
|
|
281
|
+
ctx.tools.register(defineTool({
|
|
282
|
+
name: "terminal_send",
|
|
283
|
+
description: "Send text to a persistent terminal. By default Enter is submitted and the call waits for a prompt, stdin wait, output silence, timeout, or session exit." + (enableRunInBackground ? " Background mode returns a job id for job_output/job_kill." : ""),
|
|
284
|
+
parameters: {
|
|
285
|
+
sessionId: {
|
|
286
|
+
type: "string",
|
|
287
|
+
required: true,
|
|
288
|
+
description: "Terminal session id returned by terminal_open or terminal_list."
|
|
289
|
+
},
|
|
290
|
+
text: {
|
|
291
|
+
type: "string",
|
|
292
|
+
required: true,
|
|
293
|
+
description: "UTF-8 text to write to the terminal."
|
|
294
|
+
},
|
|
295
|
+
submit: {
|
|
296
|
+
type: "boolean",
|
|
297
|
+
description: "Submit Enter after text (default true). Set false for control characters or incomplete REPL input."
|
|
298
|
+
},
|
|
299
|
+
...enableRunInBackground ? { run_in_background: {
|
|
300
|
+
type: "boolean",
|
|
301
|
+
description: "Return a job id immediately; collect with job_output or stop with job_kill."
|
|
302
|
+
} } : {}
|
|
303
|
+
},
|
|
304
|
+
finalizeContent,
|
|
305
|
+
output: {
|
|
306
|
+
schema: { oneOf: [BACKGROUND_TASK_OUTPUT_SCHEMA, {
|
|
307
|
+
type: "object",
|
|
308
|
+
additionalProperties: false,
|
|
309
|
+
properties: {
|
|
310
|
+
kind: {
|
|
311
|
+
type: "string",
|
|
312
|
+
required: true,
|
|
313
|
+
const: "foreground"
|
|
314
|
+
},
|
|
315
|
+
viewport: {
|
|
316
|
+
type: "string",
|
|
317
|
+
required: true
|
|
318
|
+
},
|
|
319
|
+
waitReason: {
|
|
320
|
+
type: "string",
|
|
321
|
+
required: true,
|
|
322
|
+
enum: [
|
|
323
|
+
"stdin_read",
|
|
324
|
+
"inferred_idle",
|
|
325
|
+
"timeout",
|
|
326
|
+
"session_exit"
|
|
327
|
+
]
|
|
328
|
+
},
|
|
329
|
+
sessionStatus: {
|
|
330
|
+
...SESSION_STATUS_SCHEMA,
|
|
331
|
+
required: true
|
|
332
|
+
},
|
|
333
|
+
truncated: {
|
|
334
|
+
type: "boolean",
|
|
335
|
+
required: true
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}] },
|
|
339
|
+
render: (_args, value) => [{
|
|
340
|
+
type: "text",
|
|
341
|
+
text: value.kind === "background" ? `started background job ${value.jobId}` : renderSend(value, maxResultBytes)
|
|
342
|
+
}],
|
|
343
|
+
presentationMeta: (_args, value) => value.kind === "foreground" ? {
|
|
344
|
+
viewport: value.viewport,
|
|
345
|
+
waitReason: value.waitReason,
|
|
346
|
+
sessionStatus: value.sessionStatus,
|
|
347
|
+
truncated: value.truncated
|
|
348
|
+
} : null
|
|
349
|
+
},
|
|
350
|
+
async execute(args, exec) {
|
|
351
|
+
const owner = requireAgent(exec.agent);
|
|
352
|
+
const id = sessionId(args);
|
|
353
|
+
const request = {
|
|
354
|
+
text: args.text,
|
|
355
|
+
submit: args.submit ?? true
|
|
356
|
+
};
|
|
357
|
+
if (args.run_in_background === true) {
|
|
358
|
+
if (!enableRunInBackground) throw new Error("background terminal sends are disabled by tool-terminal configuration");
|
|
359
|
+
const jobs = ctx.get("jobs");
|
|
360
|
+
if (jobs === void 0) throw new Error("background terminal sends require @deepseek-ai/dsh-jobs and @deepseek-ai/dsh-tool-jobs");
|
|
361
|
+
let cancelRequested = false;
|
|
362
|
+
return {
|
|
363
|
+
kind: "background",
|
|
364
|
+
jobId: jobs.start({
|
|
365
|
+
kind: "pty-send",
|
|
366
|
+
label: `${id}: ${args.text || "(input)"}`,
|
|
367
|
+
owner,
|
|
368
|
+
outputLimitBytes: maxResultBytes,
|
|
369
|
+
run: () => {
|
|
370
|
+
const operation = ctx.terminals.startSend(owner, id, request);
|
|
371
|
+
return {
|
|
372
|
+
cancel: () => {
|
|
373
|
+
cancelRequested = true;
|
|
374
|
+
operation.cancel();
|
|
375
|
+
},
|
|
376
|
+
done: operation.done.then((result) => ({
|
|
377
|
+
status: cancelRequested ? "killed" : "completed",
|
|
378
|
+
detail: sendDetail(result)
|
|
379
|
+
}), (error) => ({
|
|
380
|
+
status: "failed",
|
|
381
|
+
detail: String(error)
|
|
382
|
+
})),
|
|
383
|
+
readOutput: () => renderSendRead(operation.readOutput())
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
})
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
const result = await ctx.terminals.startSend(owner, id, {
|
|
390
|
+
...request,
|
|
391
|
+
signal: exec.signal
|
|
392
|
+
}).done;
|
|
393
|
+
if (exec.signal.aborted) throw new Error("terminal send aborted");
|
|
394
|
+
return {
|
|
395
|
+
kind: "foreground",
|
|
396
|
+
...result
|
|
397
|
+
};
|
|
398
|
+
},
|
|
399
|
+
presentCall(args) {
|
|
400
|
+
const parsed = args;
|
|
401
|
+
if (parsed.run_in_background === true) return {
|
|
402
|
+
card: "generic",
|
|
403
|
+
title: `Send to terminal ${parsed.sessionId} in background`,
|
|
404
|
+
kind: "execute",
|
|
405
|
+
rawInput: parsed.text
|
|
406
|
+
};
|
|
407
|
+
return {
|
|
408
|
+
card: "terminal",
|
|
409
|
+
title: parsed.text || "(send input)",
|
|
410
|
+
description: `Terminal ${parsed.sessionId}`
|
|
411
|
+
};
|
|
412
|
+
},
|
|
413
|
+
presentResult(args, result) {
|
|
414
|
+
if (args.run_in_background === true || result.isError) return void 0;
|
|
415
|
+
const raw = rawContentText(result.content);
|
|
416
|
+
return raw === void 0 ? void 0 : {
|
|
417
|
+
card: "terminal",
|
|
418
|
+
output: raw
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
}));
|
|
422
|
+
ctx.tools.register(defineTool({
|
|
423
|
+
name: "terminal_read",
|
|
424
|
+
description: "Read a bounded page of retained output from a persistent terminal without sending input.",
|
|
425
|
+
parameters: {
|
|
426
|
+
sessionId: {
|
|
427
|
+
type: "string",
|
|
428
|
+
required: true,
|
|
429
|
+
description: "Terminal session id."
|
|
430
|
+
},
|
|
431
|
+
offset: {
|
|
432
|
+
type: "number",
|
|
433
|
+
description: "Newest-relative line offset (default 0)."
|
|
434
|
+
},
|
|
435
|
+
count: {
|
|
436
|
+
type: "number",
|
|
437
|
+
description: "Requested line count (default 500; backend caps apply)."
|
|
438
|
+
}
|
|
439
|
+
},
|
|
440
|
+
finalizeContent,
|
|
441
|
+
output: {
|
|
442
|
+
schema: {
|
|
443
|
+
type: "object",
|
|
444
|
+
additionalProperties: false,
|
|
445
|
+
properties: {
|
|
446
|
+
text: {
|
|
447
|
+
type: "string",
|
|
448
|
+
required: true
|
|
449
|
+
},
|
|
450
|
+
totalLines: {
|
|
451
|
+
type: "integer",
|
|
452
|
+
required: true
|
|
453
|
+
},
|
|
454
|
+
lineBegin: {
|
|
455
|
+
type: "integer",
|
|
456
|
+
required: true
|
|
457
|
+
},
|
|
458
|
+
lineEnd: {
|
|
459
|
+
type: "integer",
|
|
460
|
+
required: true
|
|
461
|
+
},
|
|
462
|
+
truncated: {
|
|
463
|
+
type: "boolean",
|
|
464
|
+
required: true
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
},
|
|
468
|
+
render: (_args, value) => [{
|
|
469
|
+
type: "text",
|
|
470
|
+
text: renderRead(value, maxResultBytes)
|
|
471
|
+
}]
|
|
472
|
+
},
|
|
473
|
+
execute(args, exec) {
|
|
474
|
+
const result = ctx.terminals.read(requireAgent(exec.agent), sessionId(args), {
|
|
475
|
+
...args.offset !== void 0 ? { offset: args.offset } : {},
|
|
476
|
+
...args.count !== void 0 ? { count: args.count } : {}
|
|
477
|
+
});
|
|
478
|
+
return Promise.resolve(result);
|
|
479
|
+
},
|
|
480
|
+
presentCall: (args) => ({
|
|
481
|
+
card: "generic",
|
|
482
|
+
title: `Read terminal ${args.sessionId}`,
|
|
483
|
+
kind: "read",
|
|
484
|
+
rawInput: args
|
|
485
|
+
})
|
|
486
|
+
}));
|
|
487
|
+
ctx.tools.register(defineTool({
|
|
488
|
+
name: "terminal_signal",
|
|
489
|
+
description: "Send an allowed signal to the current foreground process group of a persistent terminal.",
|
|
490
|
+
parameters: {
|
|
491
|
+
sessionId: {
|
|
492
|
+
type: "string",
|
|
493
|
+
required: true,
|
|
494
|
+
description: "Terminal session id."
|
|
495
|
+
},
|
|
496
|
+
signal: {
|
|
497
|
+
type: "string",
|
|
498
|
+
required: true,
|
|
499
|
+
enum: [
|
|
500
|
+
"SIGINT",
|
|
501
|
+
"SIGTERM",
|
|
502
|
+
"SIGKILL",
|
|
503
|
+
"SIGTSTP",
|
|
504
|
+
"SIGHUP"
|
|
505
|
+
],
|
|
506
|
+
description: "Signal to deliver. Shell-targeted SIGKILL is rejected; use terminal_close."
|
|
507
|
+
}
|
|
508
|
+
},
|
|
509
|
+
finalizeContent,
|
|
510
|
+
output: {
|
|
511
|
+
schema: {
|
|
512
|
+
type: "object",
|
|
513
|
+
additionalProperties: false,
|
|
514
|
+
properties: {
|
|
515
|
+
delivered: {
|
|
516
|
+
type: "boolean",
|
|
517
|
+
required: true,
|
|
518
|
+
const: true
|
|
519
|
+
},
|
|
520
|
+
targetPgid: {
|
|
521
|
+
type: "integer",
|
|
522
|
+
required: true
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
},
|
|
526
|
+
render: (args, value) => [{
|
|
527
|
+
type: "text",
|
|
528
|
+
text: `delivered ${args.signal} to foreground process group ${value.targetPgid}`
|
|
529
|
+
}]
|
|
530
|
+
},
|
|
531
|
+
async execute(args, exec) {
|
|
532
|
+
return ctx.terminals.signal(requireAgent(exec.agent), sessionId(args), args.signal);
|
|
533
|
+
},
|
|
534
|
+
presentCall: (args) => ({
|
|
535
|
+
card: "generic",
|
|
536
|
+
title: `Signal terminal ${args.sessionId}`,
|
|
537
|
+
kind: "execute",
|
|
538
|
+
rawInput: args
|
|
539
|
+
})
|
|
540
|
+
}));
|
|
541
|
+
ctx.tools.register(defineTool({
|
|
542
|
+
name: "terminal_close",
|
|
543
|
+
description: "Close one persistent terminal and wait until its captured owned process tree is gone.",
|
|
544
|
+
parameters: { sessionId: {
|
|
545
|
+
type: "string",
|
|
546
|
+
required: true,
|
|
547
|
+
description: "Terminal session id."
|
|
548
|
+
} },
|
|
549
|
+
finalizeContent,
|
|
550
|
+
output: {
|
|
551
|
+
schema: {
|
|
552
|
+
type: "object",
|
|
553
|
+
additionalProperties: false,
|
|
554
|
+
properties: {
|
|
555
|
+
sessionId: {
|
|
556
|
+
type: "string",
|
|
557
|
+
required: true
|
|
558
|
+
},
|
|
559
|
+
outcome: {
|
|
560
|
+
type: "string",
|
|
561
|
+
required: true,
|
|
562
|
+
enum: ["closed", "already-closing"]
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
},
|
|
566
|
+
render: (_args, value) => [{
|
|
567
|
+
type: "text",
|
|
568
|
+
text: value.outcome === "closed" ? `closed terminal session ${value.sessionId}` : `terminal session ${value.sessionId} was already closing`
|
|
569
|
+
}]
|
|
570
|
+
},
|
|
571
|
+
async execute(args, exec) {
|
|
572
|
+
const id = sessionId(args);
|
|
573
|
+
return {
|
|
574
|
+
sessionId: id,
|
|
575
|
+
outcome: await ctx.terminals.kill(requireAgent(exec.agent), id) ? "closed" : "already-closing"
|
|
576
|
+
};
|
|
577
|
+
},
|
|
578
|
+
presentCall: (args) => ({
|
|
579
|
+
card: "generic",
|
|
580
|
+
title: `Close terminal ${args.sessionId}`,
|
|
581
|
+
kind: "delete"
|
|
582
|
+
})
|
|
583
|
+
}));
|
|
584
|
+
ctx.tools.register(defineTool({
|
|
585
|
+
name: "terminal_list",
|
|
586
|
+
description: "List persistent terminal sessions owned by the current agent.",
|
|
587
|
+
parameters: {},
|
|
588
|
+
finalizeContent,
|
|
589
|
+
output: {
|
|
590
|
+
schema: {
|
|
591
|
+
type: "array",
|
|
592
|
+
items: SESSION_SNAPSHOT_SCHEMA
|
|
593
|
+
},
|
|
594
|
+
render: (_args, value) => [{
|
|
595
|
+
type: "text",
|
|
596
|
+
text: renderList(value, maxResultBytes)
|
|
597
|
+
}]
|
|
598
|
+
},
|
|
599
|
+
execute(_args, exec) {
|
|
600
|
+
return Promise.resolve(ctx.terminals.list(requireAgent(exec.agent)));
|
|
601
|
+
},
|
|
602
|
+
presentCall: () => ({
|
|
603
|
+
card: "generic",
|
|
604
|
+
title: "List terminal sessions",
|
|
605
|
+
kind: "read"
|
|
606
|
+
})
|
|
607
|
+
}));
|
|
608
|
+
}
|
|
609
|
+
//#endregion
|
|
610
|
+
export { Config, DEFAULT_MAX_RESULT_BYTES, MIN_MAX_RESULT_BYTES, apply, inject, name };
|
package/lib/invariant.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region lib/types/invariant.js
|
|
2
|
+
/**
|
|
3
|
+
* Package-owned invariant companion for `@deepseek-ai/dsh-tool-terminal`.
|
|
4
|
+
* @module @deepseek-ai/dsh-tool-terminal/invariant
|
|
5
|
+
*/
|
|
6
|
+
const PACKAGE_NAME = "@deepseek-ai/dsh-tool-terminal";
|
|
7
|
+
/** Cordis companion plugin name. */
|
|
8
|
+
const name = "tool-terminal-invariant";
|
|
9
|
+
/** Service required before the companion can reserve package ownership. */
|
|
10
|
+
const inject = ["invariants"];
|
|
11
|
+
/**
|
|
12
|
+
* No runtime invariant: this stateless adapter contributes tools and prompt guidance, while PTY
|
|
13
|
+
* lifecycle and background-job relationships remain owned by the services it composes.
|
|
14
|
+
*/
|
|
15
|
+
const install = () => {};
|
|
16
|
+
/**
|
|
17
|
+
* Register this package's invariant companion.
|
|
18
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
19
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
20
|
+
*/
|
|
21
|
+
const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
22
|
+
//#endregion
|
|
23
|
+
export { apply, inject, name };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Six model-facing persistent terminal tools. Owner identity comes from the exact
|
|
3
|
+
* tool execution Agent; generic `ctx.jobs` owns background ids and collection.
|
|
4
|
+
* @module @deepseek-ai/dsh-tool-terminal
|
|
5
|
+
*/
|
|
6
|
+
import { Context } from '@deepseek-ai/cordis';
|
|
7
|
+
import z from '@deepseek-ai/schemastery';
|
|
8
|
+
declare module '@deepseek-ai/dsh-jobs' {
|
|
9
|
+
interface JobKindMap {
|
|
10
|
+
'pty-send': 'pty-send';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/** Cordis plugin name. */
|
|
14
|
+
export declare const name = "tool-terminal";
|
|
15
|
+
/** Required capability, registry, and prompt services. */
|
|
16
|
+
export declare const inject: string[];
|
|
17
|
+
/** Default cap for one complete model-facing terminal result. */
|
|
18
|
+
export declare const DEFAULT_MAX_RESULT_BYTES: number;
|
|
19
|
+
/** Smallest cap that preserves every counter-backed PTY and job id in its creation acknowledgement. */
|
|
20
|
+
export declare const MIN_MAX_RESULT_BYTES = 64;
|
|
21
|
+
/** Model-facing terminal tool configuration. */
|
|
22
|
+
export interface Config {
|
|
23
|
+
/** Expose `run_in_background` and accept background sends (default true). */
|
|
24
|
+
enableRunInBackground?: boolean;
|
|
25
|
+
/** Maximum UTF-8 bytes in one complete terminal or task-output result. */
|
|
26
|
+
maxResultBytes?: number;
|
|
27
|
+
}
|
|
28
|
+
/** Schemastery configuration for the terminal tool consumer. */
|
|
29
|
+
export declare const Config: z<Config>;
|
|
30
|
+
/** Register all terminal tools and the minimal usage guidance. */
|
|
31
|
+
export declare function apply(ctx: Context, config?: Config): void;
|
|
32
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-owned invariant companion for `@deepseek-ai/dsh-tool-terminal`.
|
|
3
|
+
* @module @deepseek-ai/dsh-tool-terminal/invariant
|
|
4
|
+
*/
|
|
5
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
6
|
+
/** Cordis companion plugin name. */
|
|
7
|
+
export declare const name = "tool-terminal-invariant";
|
|
8
|
+
/** Service required before the companion can reserve package ownership. */
|
|
9
|
+
export declare const inject: string[];
|
|
10
|
+
/**
|
|
11
|
+
* Register this package's invariant companion.
|
|
12
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
13
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
14
|
+
*/
|
|
15
|
+
export declare const apply: (ctx: Context) => Promise<() => void>;
|
|
16
|
+
//# sourceMappingURL=invariant.d.ts.map
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/** Model and UI rendering for persistent terminal tool results. */
|
|
2
|
+
interface RenderedSessionStatusRunning {
|
|
3
|
+
kind: 'running';
|
|
4
|
+
}
|
|
5
|
+
interface RenderedSessionStatusExited {
|
|
6
|
+
kind: 'exited';
|
|
7
|
+
exitCode: number | null;
|
|
8
|
+
signal: string | null;
|
|
9
|
+
}
|
|
10
|
+
type RenderedSessionStatus = RenderedSessionStatusRunning | RenderedSessionStatusExited;
|
|
11
|
+
interface RenderedSessionSnapshot {
|
|
12
|
+
sessionId: string;
|
|
13
|
+
name?: string;
|
|
14
|
+
type: string;
|
|
15
|
+
pid?: number;
|
|
16
|
+
status: RenderedSessionStatus;
|
|
17
|
+
}
|
|
18
|
+
interface RenderedSpawnResult extends RenderedSessionSnapshot {
|
|
19
|
+
motd: string;
|
|
20
|
+
}
|
|
21
|
+
interface RenderedSendResult {
|
|
22
|
+
viewport: string;
|
|
23
|
+
waitReason: 'stdin_read' | 'inferred_idle' | 'timeout' | 'session_exit';
|
|
24
|
+
sessionStatus: RenderedSessionStatus;
|
|
25
|
+
truncated: boolean;
|
|
26
|
+
}
|
|
27
|
+
interface RenderedSendRead {
|
|
28
|
+
delta: string;
|
|
29
|
+
truncated: boolean;
|
|
30
|
+
}
|
|
31
|
+
interface RenderedReadResult {
|
|
32
|
+
text: string;
|
|
33
|
+
totalLines: number;
|
|
34
|
+
lineBegin: number;
|
|
35
|
+
lineEnd: number;
|
|
36
|
+
truncated: boolean;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Bound one complete terminal acknowledgement while preserving UTF-8 cuts.
|
|
40
|
+
* @param text - complete acknowledgement text.
|
|
41
|
+
* @param maxBytes - positive final result cap.
|
|
42
|
+
* @returns bounded text with a truncation marker when it fits.
|
|
43
|
+
*/
|
|
44
|
+
export declare function boundTerminalText(text: string, maxBytes: number): string;
|
|
45
|
+
/**
|
|
46
|
+
* Render one created session and its bounded MOTD.
|
|
47
|
+
* @param result - published spawn result.
|
|
48
|
+
* @param maxBytes - complete UTF-8 result cap.
|
|
49
|
+
* @returns Model-facing session acknowledgement.
|
|
50
|
+
*/
|
|
51
|
+
export declare function renderSpawn(result: RenderedSpawnResult, maxBytes: number): string;
|
|
52
|
+
/**
|
|
53
|
+
* Render one settled interactive send.
|
|
54
|
+
* @param result - settled send outcome.
|
|
55
|
+
* @param maxBytes - complete UTF-8 result cap.
|
|
56
|
+
* @returns Terminal output plus wait/session markers.
|
|
57
|
+
*/
|
|
58
|
+
export declare function renderSend(result: RenderedSendResult, maxBytes: number): string;
|
|
59
|
+
/**
|
|
60
|
+
* Render one incremental background operation read.
|
|
61
|
+
* @param read - consuming operation delta.
|
|
62
|
+
* @returns Delta plus its upstream truncation marker. The generic task control
|
|
63
|
+
* applies the producer's complete-result cap after adding job status.
|
|
64
|
+
*/
|
|
65
|
+
export declare function renderSendRead(read: RenderedSendRead): string;
|
|
66
|
+
/**
|
|
67
|
+
* Render one bounded historical page.
|
|
68
|
+
* @param result - retained scrollback page.
|
|
69
|
+
* @param maxBytes - complete UTF-8 result cap.
|
|
70
|
+
* @returns Page text plus pagination and truncation markers.
|
|
71
|
+
*/
|
|
72
|
+
export declare function renderRead(result: RenderedReadResult, maxBytes: number): string;
|
|
73
|
+
/**
|
|
74
|
+
* Render owner-visible live sessions.
|
|
75
|
+
* @param sessions - fresh owner-scoped snapshots.
|
|
76
|
+
* @param maxBytes - complete UTF-8 result cap.
|
|
77
|
+
* @returns One line per session or the empty marker.
|
|
78
|
+
*/
|
|
79
|
+
export declare function renderList(sessions: readonly RenderedSessionSnapshot[], maxBytes: number): string;
|
|
80
|
+
export {};
|
|
81
|
+
//# sourceMappingURL=render.d.ts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@deepseek-ai/dsh-tool-terminal",
|
|
3
|
+
"description": "Six model-facing persistent PTY tools with owner isolation and generic background-job integration",
|
|
4
|
+
"version": "0.0.1-rc.5",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "restricted"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
|
|
11
|
+
"directory": "packages/terminal/tool-terminal"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "lib/index.js",
|
|
15
|
+
"types": "lib/types/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./lib/types/index.d.ts",
|
|
19
|
+
"default": "./lib/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./invariant": {
|
|
22
|
+
"types": "./lib/types/invariant.d.ts",
|
|
23
|
+
"default": "./lib/invariant.js"
|
|
24
|
+
},
|
|
25
|
+
"./src/*": "./src/*",
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"lib/index.js",
|
|
30
|
+
"lib/invariant.js",
|
|
31
|
+
"lib/types/**/*.d.ts"
|
|
32
|
+
],
|
|
33
|
+
"license": "BSD-3-Clause",
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@deepseek-ai/schemastery": "^3.18.1-rc.4"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"@deepseek-ai/dsh-agent": "^0.0.1-rc.5",
|
|
39
|
+
"@deepseek-ai/dsh-invariants": "^0.0.1-rc.5",
|
|
40
|
+
"@deepseek-ai/dsh-llm": "^0.0.1-rc.5",
|
|
41
|
+
"@deepseek-ai/dsh-terminal": "^0.0.1-rc.5",
|
|
42
|
+
"@deepseek-ai/dsh-output-retention": "^0.0.1-rc.5",
|
|
43
|
+
"@deepseek-ai/dsh-system-prompt": "^0.0.1-rc.5",
|
|
44
|
+
"@deepseek-ai/dsh-jobs": "^0.0.1-rc.5",
|
|
45
|
+
"@deepseek-ai/dsh-tools": "^0.0.1-rc.5",
|
|
46
|
+
"@deepseek-ai/cordis": "^4.0.1-rc.4"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@deepseek-ai/cordis-plugin-include": "^1.0.6-rc.4",
|
|
50
|
+
"@deepseek-ai/cordis-plugin-loader": "^1.0.2-rc.4",
|
|
51
|
+
"@deepseek-ai/dsh-agent": "^0.0.1-rc.5",
|
|
52
|
+
"@deepseek-ai/dsh-invariants": "^0.0.1-rc.5",
|
|
53
|
+
"@deepseek-ai/dsh-llm": "^0.0.1-rc.5",
|
|
54
|
+
"@deepseek-ai/dsh-terminal": "^0.0.1-rc.5",
|
|
55
|
+
"@deepseek-ai/dsh-terminal-bash": "^0.0.1-rc.5",
|
|
56
|
+
"@deepseek-ai/dsh-output-retention": "^0.0.1-rc.5",
|
|
57
|
+
"@deepseek-ai/dsh-sandbox": "^0.0.1-rc.5",
|
|
58
|
+
"@deepseek-ai/dsh-sandbox-policy": "^0.0.1-rc.5",
|
|
59
|
+
"@deepseek-ai/dsh-session": "^0.0.1-rc.5",
|
|
60
|
+
"@deepseek-ai/dsh-subprocess-local": "^0.0.1-rc.5",
|
|
61
|
+
"@deepseek-ai/dsh-system-prompt": "^0.0.1-rc.5",
|
|
62
|
+
"@deepseek-ai/dsh-jobs": "^0.0.1-rc.5",
|
|
63
|
+
"@deepseek-ai/dsh-jobs-local": "^0.0.1-rc.5",
|
|
64
|
+
"@deepseek-ai/dsh-tool-jobs": "^0.0.1-rc.5",
|
|
65
|
+
"@deepseek-ai/dsh-tools": "^0.0.1-rc.5",
|
|
66
|
+
"@deepseek-ai/cordis": "^4.0.1-rc.4"
|
|
67
|
+
}
|
|
68
|
+
}
|