@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.
Files changed (58) hide show
  1. package/CHANGELOG.md +91 -0
  2. package/README.md +318 -13
  3. package/README.zh.md +101 -4
  4. package/THIRD-PARTY-NOTICES.md +91 -0
  5. package/cordis.patch.yml +56 -0
  6. package/node_modules/@deepseek-ai/cosmokit/LICENSE +21 -0
  7. package/node_modules/@deepseek-ai/cosmokit/README.md +30 -0
  8. package/node_modules/@deepseek-ai/cosmokit/lib/index.js +418 -0
  9. package/node_modules/@deepseek-ai/cosmokit/lib/types/array.d.ts +15 -0
  10. package/node_modules/@deepseek-ai/cosmokit/lib/types/array.d.ts.map +1 -0
  11. package/node_modules/@deepseek-ai/cosmokit/lib/types/index.d.ts +13 -0
  12. package/node_modules/@deepseek-ai/cosmokit/lib/types/index.d.ts.map +1 -0
  13. package/node_modules/@deepseek-ai/cosmokit/lib/types/misc.d.ts +41 -0
  14. package/node_modules/@deepseek-ai/cosmokit/lib/types/misc.d.ts.map +1 -0
  15. package/node_modules/@deepseek-ai/cosmokit/lib/types/string.d.ts +89 -0
  16. package/node_modules/@deepseek-ai/cosmokit/lib/types/string.d.ts.map +1 -0
  17. package/node_modules/@deepseek-ai/cosmokit/lib/types/time.d.ts +19 -0
  18. package/node_modules/@deepseek-ai/cosmokit/lib/types/time.d.ts.map +1 -0
  19. package/node_modules/@deepseek-ai/cosmokit/lib/types/types.d.ts +42 -0
  20. package/node_modules/@deepseek-ai/cosmokit/lib/types/types.d.ts.map +1 -0
  21. package/node_modules/@deepseek-ai/cosmokit/lib/types/volatile.d.ts +40 -0
  22. package/node_modules/@deepseek-ai/cosmokit/lib/types/volatile.d.ts.map +1 -0
  23. package/node_modules/@deepseek-ai/cosmokit/package.json +32 -0
  24. package/node_modules/@deepseek-ai/cosmokit/src/array.ts +42 -0
  25. package/node_modules/@deepseek-ai/cosmokit/src/index.ts +12 -0
  26. package/node_modules/@deepseek-ai/cosmokit/src/misc.ts +78 -0
  27. package/node_modules/@deepseek-ai/cosmokit/src/string.ts +113 -0
  28. package/node_modules/@deepseek-ai/cosmokit/src/time.ts +92 -0
  29. package/node_modules/@deepseek-ai/cosmokit/src/types.ts +168 -0
  30. package/node_modules/@deepseek-ai/cosmokit/src/volatile.ts +86 -0
  31. package/node_modules/@deepseek-ai/dsh-tool-terminal/LICENSE +21 -0
  32. package/node_modules/@deepseek-ai/dsh-tool-terminal/README.i18n.yaml +100 -0
  33. package/node_modules/@deepseek-ai/dsh-tool-terminal/README.md +186 -0
  34. package/node_modules/@deepseek-ai/dsh-tool-terminal/README.zh.md +186 -0
  35. package/node_modules/@deepseek-ai/dsh-tool-terminal/lib/index.js +646 -0
  36. package/node_modules/@deepseek-ai/dsh-tool-terminal/lib/types/background.d.ts +20 -0
  37. package/node_modules/@deepseek-ai/dsh-tool-terminal/lib/types/index.d.ts +32 -0
  38. package/node_modules/@deepseek-ai/dsh-tool-terminal/lib/types/render.d.ts +81 -0
  39. package/node_modules/@deepseek-ai/dsh-tool-terminal/package.json +63 -0
  40. package/node_modules/@deepseek-ai/schemastery/LICENSE +21 -0
  41. package/node_modules/@deepseek-ai/schemastery/README.md +393 -0
  42. package/node_modules/@deepseek-ai/schemastery/lib/index.cjs +635 -0
  43. package/node_modules/@deepseek-ai/schemastery/lib/index.mjs +635 -0
  44. package/node_modules/@deepseek-ai/schemastery/lib/types/index.d.ts +211 -0
  45. package/node_modules/@deepseek-ai/schemastery/lib/types/index.d.ts.map +1 -0
  46. package/node_modules/@deepseek-ai/schemastery/package.json +39 -0
  47. package/node_modules/@deepseek-ai/schemastery/src/index.ts +962 -0
  48. package/node_modules/@standard-schema/spec/LICENSE +21 -0
  49. package/node_modules/@standard-schema/spec/README.md +198 -0
  50. package/node_modules/@standard-schema/spec/dist/index.cjs +18 -0
  51. package/node_modules/@standard-schema/spec/dist/index.d.cts +119 -0
  52. package/node_modules/@standard-schema/spec/dist/index.d.ts +119 -0
  53. package/node_modules/@standard-schema/spec/dist/index.js +0 -0
  54. package/node_modules/@standard-schema/spec/package.json +52 -0
  55. package/package.json +56 -42
  56. package/terminal-tools.js +259 -0
  57. package/terminal.js +297 -0
  58. 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