@banbolee/dsh-fish-shell 0.7.0 → 0.8.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/CHANGELOG.md +91 -0
- package/README.md +318 -13
- package/README.zh.md +101 -4
- package/THIRD-PARTY-NOTICES.md +91 -0
- package/cordis.patch.yml +56 -0
- package/node_modules/@deepseek-ai/cosmokit/LICENSE +21 -0
- package/node_modules/@deepseek-ai/cosmokit/README.md +30 -0
- package/node_modules/@deepseek-ai/cosmokit/lib/index.js +418 -0
- package/node_modules/@deepseek-ai/cosmokit/lib/types/array.d.ts +15 -0
- package/node_modules/@deepseek-ai/cosmokit/lib/types/array.d.ts.map +1 -0
- package/node_modules/@deepseek-ai/cosmokit/lib/types/index.d.ts +13 -0
- package/node_modules/@deepseek-ai/cosmokit/lib/types/index.d.ts.map +1 -0
- package/node_modules/@deepseek-ai/cosmokit/lib/types/misc.d.ts +41 -0
- package/node_modules/@deepseek-ai/cosmokit/lib/types/misc.d.ts.map +1 -0
- package/node_modules/@deepseek-ai/cosmokit/lib/types/string.d.ts +89 -0
- package/node_modules/@deepseek-ai/cosmokit/lib/types/string.d.ts.map +1 -0
- package/node_modules/@deepseek-ai/cosmokit/lib/types/time.d.ts +19 -0
- package/node_modules/@deepseek-ai/cosmokit/lib/types/time.d.ts.map +1 -0
- package/node_modules/@deepseek-ai/cosmokit/lib/types/types.d.ts +42 -0
- package/node_modules/@deepseek-ai/cosmokit/lib/types/types.d.ts.map +1 -0
- package/node_modules/@deepseek-ai/cosmokit/lib/types/volatile.d.ts +40 -0
- package/node_modules/@deepseek-ai/cosmokit/lib/types/volatile.d.ts.map +1 -0
- package/node_modules/@deepseek-ai/cosmokit/package.json +32 -0
- package/node_modules/@deepseek-ai/cosmokit/src/array.ts +42 -0
- package/node_modules/@deepseek-ai/cosmokit/src/index.ts +12 -0
- package/node_modules/@deepseek-ai/cosmokit/src/misc.ts +78 -0
- package/node_modules/@deepseek-ai/cosmokit/src/string.ts +113 -0
- package/node_modules/@deepseek-ai/cosmokit/src/time.ts +92 -0
- package/node_modules/@deepseek-ai/cosmokit/src/types.ts +168 -0
- package/node_modules/@deepseek-ai/cosmokit/src/volatile.ts +86 -0
- package/node_modules/@deepseek-ai/dsh-tool-terminal/LICENSE +21 -0
- package/node_modules/@deepseek-ai/dsh-tool-terminal/README.i18n.yaml +100 -0
- package/node_modules/@deepseek-ai/dsh-tool-terminal/README.md +186 -0
- package/node_modules/@deepseek-ai/dsh-tool-terminal/README.zh.md +186 -0
- package/node_modules/@deepseek-ai/dsh-tool-terminal/lib/index.js +646 -0
- package/node_modules/@deepseek-ai/dsh-tool-terminal/lib/types/background.d.ts +20 -0
- package/node_modules/@deepseek-ai/dsh-tool-terminal/lib/types/index.d.ts +32 -0
- package/node_modules/@deepseek-ai/dsh-tool-terminal/lib/types/render.d.ts +81 -0
- package/node_modules/@deepseek-ai/dsh-tool-terminal/package.json +63 -0
- package/node_modules/@deepseek-ai/schemastery/LICENSE +21 -0
- package/node_modules/@deepseek-ai/schemastery/README.md +393 -0
- package/node_modules/@deepseek-ai/schemastery/lib/index.cjs +635 -0
- package/node_modules/@deepseek-ai/schemastery/lib/index.mjs +635 -0
- package/node_modules/@deepseek-ai/schemastery/lib/types/index.d.ts +211 -0
- package/node_modules/@deepseek-ai/schemastery/lib/types/index.d.ts.map +1 -0
- package/node_modules/@deepseek-ai/schemastery/package.json +39 -0
- package/node_modules/@deepseek-ai/schemastery/src/index.ts +962 -0
- package/node_modules/@standard-schema/spec/LICENSE +21 -0
- package/node_modules/@standard-schema/spec/README.md +198 -0
- package/node_modules/@standard-schema/spec/dist/index.cjs +18 -0
- package/node_modules/@standard-schema/spec/dist/index.d.cts +119 -0
- package/node_modules/@standard-schema/spec/dist/index.d.ts +119 -0
- package/node_modules/@standard-schema/spec/dist/index.js +0 -0
- package/node_modules/@standard-schema/spec/package.json +52 -0
- package/package.json +56 -42
- package/terminal-tools.js +259 -0
- package/terminal.js +297 -0
- package/tool.js +1 -1
|
@@ -0,0 +1,646 @@
|
|
|
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/background.js
|
|
107
|
+
/**
|
|
108
|
+
* Generic-job adaptation for background terminal sends: the registry pull
|
|
109
|
+
* source over the backend's consuming send reader.
|
|
110
|
+
*
|
|
111
|
+
* @module @deepseek-ai/dsh-tool-terminal/background
|
|
112
|
+
*/
|
|
113
|
+
/**
|
|
114
|
+
* Adapt the backend's consuming send reader to a registry pull source. The
|
|
115
|
+
* reader has no offsets of its own — each call hands over what arrived since
|
|
116
|
+
* the previous one — so the source counts the bytes it delivered to keep the
|
|
117
|
+
* registry's cursor monotonic. It binds lazily because the send starts inside
|
|
118
|
+
* the job starter, after the registry admitted the job; a read before that
|
|
119
|
+
* point delivers nothing.
|
|
120
|
+
* @param operation - the live send, once the starter has begun it.
|
|
121
|
+
* @returns one unlabeled source over the send's bounded delta.
|
|
122
|
+
*/
|
|
123
|
+
function sendSource(operation) {
|
|
124
|
+
return { read: (fromByte) => {
|
|
125
|
+
const live = operation();
|
|
126
|
+
if (live === void 0) return {
|
|
127
|
+
text: "",
|
|
128
|
+
nextOffset: fromByte,
|
|
129
|
+
lossy: false
|
|
130
|
+
};
|
|
131
|
+
const text = renderSendRead(live.readOutput());
|
|
132
|
+
return {
|
|
133
|
+
text,
|
|
134
|
+
nextOffset: fromByte + Buffer.byteLength(text),
|
|
135
|
+
lossy: false
|
|
136
|
+
};
|
|
137
|
+
} };
|
|
138
|
+
}
|
|
139
|
+
//#endregion
|
|
140
|
+
//#region lib/types/index.js
|
|
141
|
+
/**
|
|
142
|
+
* Six model-facing persistent terminal tools. Owner identity comes from the exact
|
|
143
|
+
* tool execution Agent; generic `ctx.jobs` owns background ids and collection.
|
|
144
|
+
* @module @deepseek-ai/dsh-tool-terminal
|
|
145
|
+
*/
|
|
146
|
+
/** Cordis plugin name. */
|
|
147
|
+
const name = "tool-terminal";
|
|
148
|
+
/** Required capability, registry, and prompt services. */
|
|
149
|
+
const inject = [
|
|
150
|
+
"terminals",
|
|
151
|
+
"tools",
|
|
152
|
+
"systemPrompt"
|
|
153
|
+
];
|
|
154
|
+
/** Default cap for one complete model-facing terminal result. */
|
|
155
|
+
const DEFAULT_MAX_RESULT_BYTES = 256 * 1024;
|
|
156
|
+
/** Smallest cap that preserves every counter-backed PTY and job id in its creation acknowledgement. */
|
|
157
|
+
const MIN_MAX_RESULT_BYTES = 64;
|
|
158
|
+
/** Schemastery configuration for the terminal tool consumer. */
|
|
159
|
+
const Config = z.object({
|
|
160
|
+
enableRunInBackground: z.boolean().default(true),
|
|
161
|
+
maxResultBytes: z.number().step(1).min(64).max(Number.MAX_SAFE_INTEGER).default(DEFAULT_MAX_RESULT_BYTES)
|
|
162
|
+
});
|
|
163
|
+
const SESSION_STATUS_SCHEMA = { oneOf: [{
|
|
164
|
+
type: "object",
|
|
165
|
+
additionalProperties: false,
|
|
166
|
+
properties: { kind: {
|
|
167
|
+
type: "string",
|
|
168
|
+
required: true,
|
|
169
|
+
const: "running"
|
|
170
|
+
} }
|
|
171
|
+
}, {
|
|
172
|
+
type: "object",
|
|
173
|
+
additionalProperties: false,
|
|
174
|
+
properties: {
|
|
175
|
+
kind: {
|
|
176
|
+
type: "string",
|
|
177
|
+
required: true,
|
|
178
|
+
const: "exited"
|
|
179
|
+
},
|
|
180
|
+
exitCode: {
|
|
181
|
+
required: true,
|
|
182
|
+
oneOf: [{ type: "integer" }, { type: "null" }]
|
|
183
|
+
},
|
|
184
|
+
signal: {
|
|
185
|
+
required: true,
|
|
186
|
+
oneOf: [{ type: "string" }, { type: "null" }]
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}] };
|
|
190
|
+
const SESSION_SNAPSHOT_PROPERTIES = {
|
|
191
|
+
sessionId: {
|
|
192
|
+
type: "string",
|
|
193
|
+
required: true
|
|
194
|
+
},
|
|
195
|
+
name: { type: "string" },
|
|
196
|
+
type: {
|
|
197
|
+
type: "string",
|
|
198
|
+
required: true
|
|
199
|
+
},
|
|
200
|
+
pid: { type: "integer" },
|
|
201
|
+
status: {
|
|
202
|
+
...SESSION_STATUS_SCHEMA,
|
|
203
|
+
required: true
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
const SESSION_SNAPSHOT_SCHEMA = {
|
|
207
|
+
type: "object",
|
|
208
|
+
additionalProperties: false,
|
|
209
|
+
properties: SESSION_SNAPSHOT_PROPERTIES
|
|
210
|
+
};
|
|
211
|
+
const BACKGROUND_JOB_OUTPUT_SCHEMA = {
|
|
212
|
+
type: "object",
|
|
213
|
+
additionalProperties: false,
|
|
214
|
+
properties: {
|
|
215
|
+
kind: {
|
|
216
|
+
type: "string",
|
|
217
|
+
required: true,
|
|
218
|
+
const: "background"
|
|
219
|
+
},
|
|
220
|
+
jobId: {
|
|
221
|
+
type: "string",
|
|
222
|
+
required: true
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
function requireAgent(agent) {
|
|
227
|
+
if (agent === void 0) throw new Error("terminal tools require an initiating agent");
|
|
228
|
+
return agent;
|
|
229
|
+
}
|
|
230
|
+
function sessionId(args) {
|
|
231
|
+
if (args.sessionId.length === 0) throw new Error("sessionId must be a non-empty string");
|
|
232
|
+
return TerminalSessionId(args.sessionId);
|
|
233
|
+
}
|
|
234
|
+
function textResult(text, maxBytes) {
|
|
235
|
+
return [{
|
|
236
|
+
type: "text",
|
|
237
|
+
text: boundTerminalText(text, maxBytes)
|
|
238
|
+
}];
|
|
239
|
+
}
|
|
240
|
+
function rawContentText(content) {
|
|
241
|
+
if (content.length !== 1) return void 0;
|
|
242
|
+
const block = content[0];
|
|
243
|
+
return block?.type === "text" ? block.text : void 0;
|
|
244
|
+
}
|
|
245
|
+
function sendDetail(result) {
|
|
246
|
+
return result.sessionStatus.kind === "running" ? `wait: ${result.waitReason}` : `session exited: ${result.sessionStatus.exitCode ?? result.sessionStatus.signal ?? "unknown"}`;
|
|
247
|
+
}
|
|
248
|
+
/** Register all terminal tools and the minimal usage guidance. */
|
|
249
|
+
function apply(ctx, config = {}) {
|
|
250
|
+
const enableRunInBackground = config.enableRunInBackground ?? true;
|
|
251
|
+
const maxResultBytes = config.maxResultBytes ?? 262144;
|
|
252
|
+
if (!Number.isSafeInteger(maxResultBytes) || maxResultBytes < 64) throw new Error(`tool-terminal: maxResultBytes must be a safe integer of at least 64`);
|
|
253
|
+
const finalizeContent = (_exec, result) => {
|
|
254
|
+
const raw = rawContentText(result.content);
|
|
255
|
+
return raw === void 0 ? void 0 : textResult(raw, maxResultBytes);
|
|
256
|
+
};
|
|
257
|
+
ctx.systemPrompt.section({
|
|
258
|
+
name: "tool:pty",
|
|
259
|
+
order: ctx.systemPrompt.getSectionOrder("TOOL_PTY"),
|
|
260
|
+
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."
|
|
261
|
+
});
|
|
262
|
+
ctx.tools.register(defineTool({
|
|
263
|
+
name: "terminal_open",
|
|
264
|
+
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.",
|
|
265
|
+
parameters: {
|
|
266
|
+
type: {
|
|
267
|
+
type: "string",
|
|
268
|
+
required: true,
|
|
269
|
+
description: "Registered terminal backend type, usually \"shell\"."
|
|
270
|
+
},
|
|
271
|
+
name: {
|
|
272
|
+
type: "string",
|
|
273
|
+
description: "Optional owner-local display name such as \"main\" or \"gdb\"."
|
|
274
|
+
},
|
|
275
|
+
cwd: {
|
|
276
|
+
type: "string",
|
|
277
|
+
description: "Initial working directory. Defaults to the deployment workspace root."
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
finalizeContent,
|
|
281
|
+
output: {
|
|
282
|
+
schema: {
|
|
283
|
+
type: "object",
|
|
284
|
+
additionalProperties: false,
|
|
285
|
+
properties: {
|
|
286
|
+
...SESSION_SNAPSHOT_PROPERTIES,
|
|
287
|
+
motd: {
|
|
288
|
+
type: "string",
|
|
289
|
+
required: true
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
render: (_args, value) => [{
|
|
294
|
+
type: "text",
|
|
295
|
+
text: renderSpawn(value, maxResultBytes)
|
|
296
|
+
}]
|
|
297
|
+
},
|
|
298
|
+
async execute(args, exec) {
|
|
299
|
+
if (args.type.length === 0) throw new Error("type must be a non-empty string");
|
|
300
|
+
return await ctx.terminals.spawn(requireAgent(exec.agent), {
|
|
301
|
+
type: args.type,
|
|
302
|
+
...args.name !== void 0 ? { name: args.name } : {},
|
|
303
|
+
...args.cwd !== void 0 ? { cwd: args.cwd } : {}
|
|
304
|
+
}, exec.signal);
|
|
305
|
+
},
|
|
306
|
+
presentCall: (args) => {
|
|
307
|
+
const parsed = args;
|
|
308
|
+
return {
|
|
309
|
+
card: "generic",
|
|
310
|
+
title: `Open terminal ${parsed.name ?? parsed.type}`,
|
|
311
|
+
kind: "execute"
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
}));
|
|
315
|
+
ctx.tools.register(defineTool({
|
|
316
|
+
name: "terminal_send",
|
|
317
|
+
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." : ""),
|
|
318
|
+
parameters: {
|
|
319
|
+
sessionId: {
|
|
320
|
+
type: "string",
|
|
321
|
+
required: true,
|
|
322
|
+
description: "Terminal session id returned by terminal_open or terminal_list."
|
|
323
|
+
},
|
|
324
|
+
text: {
|
|
325
|
+
type: "string",
|
|
326
|
+
required: true,
|
|
327
|
+
description: "UTF-8 text to write to the terminal."
|
|
328
|
+
},
|
|
329
|
+
submit: {
|
|
330
|
+
type: "boolean",
|
|
331
|
+
description: "Submit Enter after text (default true). Set false for control characters or incomplete REPL input."
|
|
332
|
+
},
|
|
333
|
+
...enableRunInBackground ? { run_in_background: {
|
|
334
|
+
type: "boolean",
|
|
335
|
+
description: "Return a job id immediately; collect with job_output or stop with job_kill."
|
|
336
|
+
} } : {}
|
|
337
|
+
},
|
|
338
|
+
finalizeContent,
|
|
339
|
+
output: {
|
|
340
|
+
schema: { oneOf: [BACKGROUND_JOB_OUTPUT_SCHEMA, {
|
|
341
|
+
type: "object",
|
|
342
|
+
additionalProperties: false,
|
|
343
|
+
properties: {
|
|
344
|
+
kind: {
|
|
345
|
+
type: "string",
|
|
346
|
+
required: true,
|
|
347
|
+
const: "foreground"
|
|
348
|
+
},
|
|
349
|
+
viewport: {
|
|
350
|
+
type: "string",
|
|
351
|
+
required: true
|
|
352
|
+
},
|
|
353
|
+
waitReason: {
|
|
354
|
+
type: "string",
|
|
355
|
+
required: true,
|
|
356
|
+
enum: [
|
|
357
|
+
"stdin_read",
|
|
358
|
+
"inferred_idle",
|
|
359
|
+
"timeout",
|
|
360
|
+
"session_exit"
|
|
361
|
+
]
|
|
362
|
+
},
|
|
363
|
+
sessionStatus: {
|
|
364
|
+
...SESSION_STATUS_SCHEMA,
|
|
365
|
+
required: true
|
|
366
|
+
},
|
|
367
|
+
truncated: {
|
|
368
|
+
type: "boolean",
|
|
369
|
+
required: true
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}] },
|
|
373
|
+
render: (_args, value) => [{
|
|
374
|
+
type: "text",
|
|
375
|
+
text: value.kind === "background" ? `started background job ${value.jobId}` : renderSend(value, maxResultBytes)
|
|
376
|
+
}],
|
|
377
|
+
presentationMeta: (_args, value) => value.kind === "foreground" ? {
|
|
378
|
+
viewport: value.viewport,
|
|
379
|
+
waitReason: value.waitReason,
|
|
380
|
+
sessionStatus: value.sessionStatus,
|
|
381
|
+
truncated: value.truncated
|
|
382
|
+
} : null
|
|
383
|
+
},
|
|
384
|
+
async execute(args, exec) {
|
|
385
|
+
const owner = requireAgent(exec.agent);
|
|
386
|
+
const id = sessionId(args);
|
|
387
|
+
const request = {
|
|
388
|
+
text: args.text,
|
|
389
|
+
submit: args.submit ?? true
|
|
390
|
+
};
|
|
391
|
+
if (args.run_in_background === true) {
|
|
392
|
+
if (!enableRunInBackground) throw new Error("background terminal sends are disabled by tool-terminal configuration");
|
|
393
|
+
const jobs = ctx.get("jobs");
|
|
394
|
+
if (jobs === void 0) throw new Error("background terminal sends require @deepseek-ai/dsh-jobs and @deepseek-ai/dsh-tool-jobs");
|
|
395
|
+
let cancelRequested = false;
|
|
396
|
+
let operation;
|
|
397
|
+
return {
|
|
398
|
+
kind: "background",
|
|
399
|
+
jobId: jobs.start({
|
|
400
|
+
kind: "pty-send",
|
|
401
|
+
label: `${id}: ${args.text || "(input)"}`,
|
|
402
|
+
owner: owner.id,
|
|
403
|
+
outputLimitBytes: maxResultBytes,
|
|
404
|
+
output: [sendSource(() => operation)],
|
|
405
|
+
run: () => {
|
|
406
|
+
const started = ctx.terminals.startSend(owner, id, request);
|
|
407
|
+
operation = started;
|
|
408
|
+
return {
|
|
409
|
+
cancel: () => {
|
|
410
|
+
cancelRequested = true;
|
|
411
|
+
started.cancel();
|
|
412
|
+
},
|
|
413
|
+
done: started.done.then((result) => ({
|
|
414
|
+
status: cancelRequested ? "killed" : "completed",
|
|
415
|
+
detail: sendDetail(result)
|
|
416
|
+
}), (error) => ({
|
|
417
|
+
status: "failed",
|
|
418
|
+
detail: String(error)
|
|
419
|
+
}))
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
})
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
const result = await ctx.terminals.startSend(owner, id, {
|
|
426
|
+
...request,
|
|
427
|
+
signal: exec.signal
|
|
428
|
+
}).done;
|
|
429
|
+
if (exec.signal.aborted) throw new Error("terminal send aborted");
|
|
430
|
+
return {
|
|
431
|
+
kind: "foreground",
|
|
432
|
+
...result
|
|
433
|
+
};
|
|
434
|
+
},
|
|
435
|
+
presentCall(args) {
|
|
436
|
+
const parsed = args;
|
|
437
|
+
if (parsed.run_in_background === true) return {
|
|
438
|
+
card: "generic",
|
|
439
|
+
title: `Send to terminal ${parsed.sessionId} in background`,
|
|
440
|
+
kind: "execute",
|
|
441
|
+
rawInput: parsed.text
|
|
442
|
+
};
|
|
443
|
+
return {
|
|
444
|
+
card: "terminal",
|
|
445
|
+
title: parsed.text || "(send input)",
|
|
446
|
+
description: `Terminal ${parsed.sessionId}`
|
|
447
|
+
};
|
|
448
|
+
},
|
|
449
|
+
presentResult(args, result) {
|
|
450
|
+
if (args.run_in_background === true || result.isError) return void 0;
|
|
451
|
+
const raw = rawContentText(result.content);
|
|
452
|
+
return raw === void 0 ? void 0 : {
|
|
453
|
+
card: "terminal",
|
|
454
|
+
output: raw
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
}));
|
|
458
|
+
ctx.tools.register(defineTool({
|
|
459
|
+
name: "terminal_read",
|
|
460
|
+
description: "Read a bounded page of retained output from a persistent terminal without sending input.",
|
|
461
|
+
parameters: {
|
|
462
|
+
sessionId: {
|
|
463
|
+
type: "string",
|
|
464
|
+
required: true,
|
|
465
|
+
description: "Terminal session id."
|
|
466
|
+
},
|
|
467
|
+
offset: {
|
|
468
|
+
type: "number",
|
|
469
|
+
description: "Newest-relative line offset (default 0)."
|
|
470
|
+
},
|
|
471
|
+
count: {
|
|
472
|
+
type: "number",
|
|
473
|
+
description: "Requested line count (default 500; backend caps apply)."
|
|
474
|
+
}
|
|
475
|
+
},
|
|
476
|
+
finalizeContent,
|
|
477
|
+
output: {
|
|
478
|
+
schema: {
|
|
479
|
+
type: "object",
|
|
480
|
+
additionalProperties: false,
|
|
481
|
+
properties: {
|
|
482
|
+
text: {
|
|
483
|
+
type: "string",
|
|
484
|
+
required: true
|
|
485
|
+
},
|
|
486
|
+
totalLines: {
|
|
487
|
+
type: "integer",
|
|
488
|
+
required: true
|
|
489
|
+
},
|
|
490
|
+
lineBegin: {
|
|
491
|
+
type: "integer",
|
|
492
|
+
required: true
|
|
493
|
+
},
|
|
494
|
+
lineEnd: {
|
|
495
|
+
type: "integer",
|
|
496
|
+
required: true
|
|
497
|
+
},
|
|
498
|
+
truncated: {
|
|
499
|
+
type: "boolean",
|
|
500
|
+
required: true
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
},
|
|
504
|
+
render: (_args, value) => [{
|
|
505
|
+
type: "text",
|
|
506
|
+
text: renderRead(value, maxResultBytes)
|
|
507
|
+
}]
|
|
508
|
+
},
|
|
509
|
+
execute(args, exec) {
|
|
510
|
+
const result = ctx.terminals.read(requireAgent(exec.agent), sessionId(args), {
|
|
511
|
+
...args.offset !== void 0 ? { offset: args.offset } : {},
|
|
512
|
+
...args.count !== void 0 ? { count: args.count } : {}
|
|
513
|
+
});
|
|
514
|
+
return Promise.resolve(result);
|
|
515
|
+
},
|
|
516
|
+
presentCall: (args) => ({
|
|
517
|
+
card: "generic",
|
|
518
|
+
title: `Read terminal ${args.sessionId}`,
|
|
519
|
+
kind: "read",
|
|
520
|
+
rawInput: args
|
|
521
|
+
})
|
|
522
|
+
}));
|
|
523
|
+
ctx.tools.register(defineTool({
|
|
524
|
+
name: "terminal_signal",
|
|
525
|
+
description: "Send an allowed signal to the current foreground process group of a persistent terminal.",
|
|
526
|
+
parameters: {
|
|
527
|
+
sessionId: {
|
|
528
|
+
type: "string",
|
|
529
|
+
required: true,
|
|
530
|
+
description: "Terminal session id."
|
|
531
|
+
},
|
|
532
|
+
signal: {
|
|
533
|
+
type: "string",
|
|
534
|
+
required: true,
|
|
535
|
+
enum: [
|
|
536
|
+
"SIGINT",
|
|
537
|
+
"SIGTERM",
|
|
538
|
+
"SIGKILL",
|
|
539
|
+
"SIGTSTP",
|
|
540
|
+
"SIGHUP"
|
|
541
|
+
],
|
|
542
|
+
description: "Signal to deliver. Shell-targeted SIGKILL is rejected; use terminal_close."
|
|
543
|
+
}
|
|
544
|
+
},
|
|
545
|
+
finalizeContent,
|
|
546
|
+
output: {
|
|
547
|
+
schema: {
|
|
548
|
+
type: "object",
|
|
549
|
+
additionalProperties: false,
|
|
550
|
+
properties: {
|
|
551
|
+
delivered: {
|
|
552
|
+
type: "boolean",
|
|
553
|
+
required: true,
|
|
554
|
+
const: true
|
|
555
|
+
},
|
|
556
|
+
targetPgid: {
|
|
557
|
+
type: "integer",
|
|
558
|
+
required: true
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
},
|
|
562
|
+
render: (args, value) => [{
|
|
563
|
+
type: "text",
|
|
564
|
+
text: `delivered ${args.signal} to foreground process group ${value.targetPgid}`
|
|
565
|
+
}]
|
|
566
|
+
},
|
|
567
|
+
async execute(args, exec) {
|
|
568
|
+
return ctx.terminals.signal(requireAgent(exec.agent), sessionId(args), args.signal);
|
|
569
|
+
},
|
|
570
|
+
presentCall: (args) => ({
|
|
571
|
+
card: "generic",
|
|
572
|
+
title: `Signal terminal ${args.sessionId}`,
|
|
573
|
+
kind: "execute",
|
|
574
|
+
rawInput: args
|
|
575
|
+
})
|
|
576
|
+
}));
|
|
577
|
+
ctx.tools.register(defineTool({
|
|
578
|
+
name: "terminal_close",
|
|
579
|
+
description: "Close one persistent terminal and wait until its captured owned process tree is gone.",
|
|
580
|
+
parameters: { sessionId: {
|
|
581
|
+
type: "string",
|
|
582
|
+
required: true,
|
|
583
|
+
description: "Terminal session id."
|
|
584
|
+
} },
|
|
585
|
+
finalizeContent,
|
|
586
|
+
output: {
|
|
587
|
+
schema: {
|
|
588
|
+
type: "object",
|
|
589
|
+
additionalProperties: false,
|
|
590
|
+
properties: {
|
|
591
|
+
sessionId: {
|
|
592
|
+
type: "string",
|
|
593
|
+
required: true
|
|
594
|
+
},
|
|
595
|
+
outcome: {
|
|
596
|
+
type: "string",
|
|
597
|
+
required: true,
|
|
598
|
+
enum: ["closed", "already-closing"]
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
},
|
|
602
|
+
render: (_args, value) => [{
|
|
603
|
+
type: "text",
|
|
604
|
+
text: value.outcome === "closed" ? `closed terminal session ${value.sessionId}` : `terminal session ${value.sessionId} was already closing`
|
|
605
|
+
}]
|
|
606
|
+
},
|
|
607
|
+
async execute(args, exec) {
|
|
608
|
+
const id = sessionId(args);
|
|
609
|
+
return {
|
|
610
|
+
sessionId: id,
|
|
611
|
+
outcome: await ctx.terminals.kill(requireAgent(exec.agent), id) ? "closed" : "already-closing"
|
|
612
|
+
};
|
|
613
|
+
},
|
|
614
|
+
presentCall: (args) => ({
|
|
615
|
+
card: "generic",
|
|
616
|
+
title: `Close terminal ${args.sessionId}`,
|
|
617
|
+
kind: "delete"
|
|
618
|
+
})
|
|
619
|
+
}));
|
|
620
|
+
ctx.tools.register(defineTool({
|
|
621
|
+
name: "terminal_list",
|
|
622
|
+
description: "List persistent terminal sessions owned by the current agent.",
|
|
623
|
+
parameters: {},
|
|
624
|
+
finalizeContent,
|
|
625
|
+
output: {
|
|
626
|
+
schema: {
|
|
627
|
+
type: "array",
|
|
628
|
+
items: SESSION_SNAPSHOT_SCHEMA
|
|
629
|
+
},
|
|
630
|
+
render: (_args, value) => [{
|
|
631
|
+
type: "text",
|
|
632
|
+
text: renderList(value, maxResultBytes)
|
|
633
|
+
}]
|
|
634
|
+
},
|
|
635
|
+
execute(_args, exec) {
|
|
636
|
+
return Promise.resolve(ctx.terminals.list(requireAgent(exec.agent)));
|
|
637
|
+
},
|
|
638
|
+
presentCall: () => ({
|
|
639
|
+
card: "generic",
|
|
640
|
+
title: "List terminal sessions",
|
|
641
|
+
kind: "read"
|
|
642
|
+
})
|
|
643
|
+
}));
|
|
644
|
+
}
|
|
645
|
+
//#endregion
|
|
646
|
+
export { Config, DEFAULT_MAX_RESULT_BYTES, MIN_MAX_RESULT_BYTES, apply, inject, name };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic-job adaptation for background terminal sends: the registry pull
|
|
3
|
+
* source over the backend's consuming send reader.
|
|
4
|
+
*
|
|
5
|
+
* @module @deepseek-ai/dsh-tool-terminal/background
|
|
6
|
+
*/
|
|
7
|
+
import type { JobOutputSource } from '@deepseek-ai/dsh-jobs';
|
|
8
|
+
import type { TerminalSendOperation } from '@deepseek-ai/dsh-terminal';
|
|
9
|
+
/**
|
|
10
|
+
* Adapt the backend's consuming send reader to a registry pull source. The
|
|
11
|
+
* reader has no offsets of its own — each call hands over what arrived since
|
|
12
|
+
* the previous one — so the source counts the bytes it delivered to keep the
|
|
13
|
+
* registry's cursor monotonic. It binds lazily because the send starts inside
|
|
14
|
+
* the job starter, after the registry admitted the job; a read before that
|
|
15
|
+
* point delivers nothing.
|
|
16
|
+
* @param operation - the live send, once the starter has begun it.
|
|
17
|
+
* @returns one unlabeled source over the send's bounded delta.
|
|
18
|
+
*/
|
|
19
|
+
export declare function sendSource(operation: () => TerminalSendOperation | undefined): JobOutputSource;
|
|
20
|
+
//# sourceMappingURL=background.d.ts.map
|
|
@@ -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
|