dsh-ssh-tui 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +87 -40
- package/README.md +71 -31
- package/cordis.patch.yml +2 -0
- package/docs/screenshots/compare.png +0 -0
- package/docs/screenshots/headless.png +0 -0
- package/docs/screenshots/workspace.png +0 -0
- package/lib/index.js.map +1 -1
- package/lib/reasoning.js +5 -3
- package/lib/reasoning.js.map +1 -1
- package/lib/subagent-model.js +82 -2
- package/lib/subagent-model.js.map +1 -1
- package/lib/tui.js +1099 -139
- package/lib/tui.js.map +1 -1
- package/lib/types/index.d.ts +2 -0
- package/lib/types/subagent-model.d.ts +17 -2
- package/lib/types/tui.d.ts +149 -5
- package/package.json +6 -2
package/lib/types/index.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ export interface Config {
|
|
|
23
23
|
provider?: string;
|
|
24
24
|
/** CLI-supplied model override; otherwise the saved default is used. */
|
|
25
25
|
model?: string;
|
|
26
|
+
/** Minimum milliseconds between paints; see DSH_TUI_PAINT_MS. */
|
|
27
|
+
paintIntervalMs?: number;
|
|
26
28
|
}
|
|
27
29
|
/**
|
|
28
30
|
* Mount the SSH TUI. The `main` agent is created here after the loader
|
|
@@ -4,15 +4,30 @@
|
|
|
4
4
|
*
|
|
5
5
|
* The provider is deliberately inherited from the running parent session:
|
|
6
6
|
* the subagent selection only overrides the provider when the user stored an
|
|
7
|
-
* explicit route.
|
|
7
|
+
* explicit route. When the parent provider changes, the TUI picks a
|
|
8
|
+
* same-family default (DeepSeek flash, Grok 4.5, otherwise the first listed
|
|
9
|
+
* lightweight model) unless `/submodel` stored an explicit model.
|
|
8
10
|
*/
|
|
9
11
|
import z from '@deepseek-ai/schemastery';
|
|
10
12
|
import type { Context } from '@deepseek-ai/cordis';
|
|
11
13
|
import { type ReasoningEffortId as ReasoningEffort } from '@deepseek-ai/dsh-llm';
|
|
12
14
|
/** Settings namespace carrying the TUI's subagent model selection. */
|
|
13
15
|
export declare const SUBAGENT_SETTINGS_NAMESPACE: import("@deepseek-ai/dsh-settings").SettingsNamespace;
|
|
14
|
-
/** Lightweight default model used for subagent children. */
|
|
16
|
+
/** Lightweight default model used for DeepSeek-family subagent children. */
|
|
15
17
|
export declare const DEFAULT_SUBAGENT_MODEL = "deepseek-v4-flash";
|
|
18
|
+
/** Preferred subagent default when the parent route is SuperGrok / xAI. */
|
|
19
|
+
export declare const DEFAULT_XAI_SUBAGENT_MODEL = "grok-4.5";
|
|
20
|
+
/**
|
|
21
|
+
* Choose the default subagent model for a parent provider, preferring a
|
|
22
|
+
* same-family listed model over a leftover DeepSeek flash id.
|
|
23
|
+
*/
|
|
24
|
+
export declare function defaultSubagentModelForProvider(provider: string, listed?: readonly string[]): string;
|
|
25
|
+
/**
|
|
26
|
+
* True when the stored subagent model still belongs to the parent provider
|
|
27
|
+
* family. An explicit leftover DeepSeek flash id after switching to xAI is
|
|
28
|
+
* treated as stale so the TUI can pick a same-family default.
|
|
29
|
+
*/
|
|
30
|
+
export declare function subagentModelMatchesProvider(provider: string, model: string, listed?: readonly string[]): boolean;
|
|
16
31
|
/** Raw settings document shape. */
|
|
17
32
|
export interface SubagentSettings {
|
|
18
33
|
/** Explicit provider override; omitted means "same provider as the parent". */
|
package/lib/types/tui.d.ts
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* `ask_user_question` prompts from the keyboard, and drives one configured
|
|
6
6
|
* agent with followup/steer.
|
|
7
7
|
*
|
|
8
|
-
* The renderer uses plain ANSI and
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* The renderer uses plain ANSI and coalesces each frame into one stdout
|
|
9
|
+
* write of dirty rows only — jump-host / proxied SSH should see one packet
|
|
10
|
+
* per paint, not one per line. Cadence is DSH_TUI_PAINT_MS (default 160).
|
|
11
11
|
*/
|
|
12
12
|
import type { Agent, ModelSelection, ModelSelectionRef } from '@deepseek-ai/dsh-agent';
|
|
13
13
|
import type { Context } from '@deepseek-ai/cordis';
|
|
@@ -50,6 +50,12 @@ export interface TuiConfig {
|
|
|
50
50
|
onSelectionChanged?: (selection: ModelSelection) => void;
|
|
51
51
|
/** Open the history-session picker immediately after mounting (--resume). */
|
|
52
52
|
resumePicker?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Minimum milliseconds between paints while a turn is streaming.
|
|
55
|
+
* Jump-host / proxied SSH can raise this so token ticks do not flood the
|
|
56
|
+
* link. Defaults from `DSH_TUI_PAINT_MS` (160).
|
|
57
|
+
*/
|
|
58
|
+
paintIntervalMs?: number;
|
|
53
59
|
}
|
|
54
60
|
type SubagentLogKind = 'user' | 'assistant' | 'tool' | 'result' | 'turn' | 'approval' | 'team' | 'system';
|
|
55
61
|
/** One child-session event folded into a parent-side subagent card. */
|
|
@@ -111,7 +117,15 @@ type Row = {
|
|
|
111
117
|
active: boolean;
|
|
112
118
|
pending: boolean;
|
|
113
119
|
todos: PlanTodoItem[];
|
|
120
|
+
planMarkdown?: string;
|
|
114
121
|
expanded: boolean;
|
|
122
|
+
/** When true the plan stays in the scrolling transcript, not the dock. */
|
|
123
|
+
archived?: boolean;
|
|
124
|
+
/**
|
|
125
|
+
* Display-only: the last turn ended while todos were still open.
|
|
126
|
+
* Does not rewrite the session log.
|
|
127
|
+
*/
|
|
128
|
+
turnLeftOpen?: boolean;
|
|
115
129
|
} | {
|
|
116
130
|
kind: 'question';
|
|
117
131
|
questionId: string;
|
|
@@ -135,7 +149,7 @@ type Row = {
|
|
|
135
149
|
kind: 'error';
|
|
136
150
|
text: string;
|
|
137
151
|
};
|
|
138
|
-
type DisplayKind = Row['kind'] | 'tool-result' | 'diff-add' | 'diff-del' | 'diff-path';
|
|
152
|
+
type DisplayKind = Row['kind'] | 'tool-result' | 'diff-add' | 'diff-del' | 'diff-path' | 'todo-done' | 'todo-active' | 'todo-pending' | 'plan-dock';
|
|
139
153
|
/** One file's change, matching the web diff-card contract (`card: 'diff'`). */
|
|
140
154
|
interface ToolDiffHunk {
|
|
141
155
|
path: string;
|
|
@@ -146,6 +160,23 @@ interface ToolDiffHunk {
|
|
|
146
160
|
export interface TuiController {
|
|
147
161
|
dispose(): Promise<void>;
|
|
148
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* Paint cadence for jump-host / proxied SSH. Token ticks coalesce into one
|
|
165
|
+
* frame; the default stays snappy, slower links raise `DSH_TUI_PAINT_MS`.
|
|
166
|
+
*/
|
|
167
|
+
export declare function resolvePaintIntervalMs(configured?: number, env?: NodeJS.ProcessEnv): number;
|
|
168
|
+
/** One incremental paint as a single stdout write (one SSH packet when corked). */
|
|
169
|
+
export declare function composePaintOutput(options: {
|
|
170
|
+
width: number;
|
|
171
|
+
height: number;
|
|
172
|
+
paintRows: readonly string[];
|
|
173
|
+
previousRows: readonly string[];
|
|
174
|
+
sizeChanged: boolean;
|
|
175
|
+
chromeChanged: boolean;
|
|
176
|
+
chromeStart: number;
|
|
177
|
+
cursorRow: number;
|
|
178
|
+
cursorColumn: number;
|
|
179
|
+
}): string;
|
|
149
180
|
/** Human-facing kind for a live LLM route. */
|
|
150
181
|
export declare function describeProviderRoute(provider: string): {
|
|
151
182
|
kind: string;
|
|
@@ -153,6 +184,31 @@ export declare function describeProviderRoute(provider: string): {
|
|
|
153
184
|
};
|
|
154
185
|
/** Routes that authenticate without a harness API-key credential. */
|
|
155
186
|
export declare function providerUsesLocalOAuth(provider: string): boolean;
|
|
187
|
+
/**
|
|
188
|
+
* Terminal cell width for one string.
|
|
189
|
+
*
|
|
190
|
+
* Match glibc wcwidth / typical UTF-8 SSH terminals: CJK ideographs and
|
|
191
|
+
* fullwidth forms occupy two cells; East-Asian Ambiguous box-drawing and
|
|
192
|
+
* ornaments (`─`, `●`, `·`, `▸`, `❯`, Braille spinners) occupy one. Counting
|
|
193
|
+
* those ambiguous glyphs as two made `repeatToWidth('─', cols)` paint a
|
|
194
|
+
* half-width rule and parked the input cursor half a cell past the text.
|
|
195
|
+
*
|
|
196
|
+
* Overflow into the input box is handled by clipping/padding painted rows to
|
|
197
|
+
* the measured column count, not by inflating glyph width.
|
|
198
|
+
*/
|
|
199
|
+
export declare function displayWidth(text: string): number;
|
|
200
|
+
/** Pad or clip one already-sanitized line so it occupies exactly `width` cells. */
|
|
201
|
+
export declare function padToWidth(text: string, width: number): string;
|
|
202
|
+
/**
|
|
203
|
+
* Pad an already-styled ANSI line to `width` cells without resetting SGR.
|
|
204
|
+
* Diff add/del rows keep their background across the whole terminal row
|
|
205
|
+
* instead of only the glyphs.
|
|
206
|
+
*/
|
|
207
|
+
export declare function padAnsiToWidth(text: string, width: number): string;
|
|
208
|
+
/** Visible width of an ANSI-styled line, ignoring CSI / OSC sequences. */
|
|
209
|
+
export declare function visibleWidth(text: string): number;
|
|
210
|
+
/** Repeat a glyph until it occupies exactly `width` cells. */
|
|
211
|
+
export declare function repeatToWidth(glyph: string, width: number): string;
|
|
156
212
|
/**
|
|
157
213
|
* Render workspace markdown into width-bounded terminal rows. Assistant
|
|
158
214
|
* replies get a bold-white base; code blocks, headings, quotes, lists, rules,
|
|
@@ -161,6 +217,12 @@ export declare function providerUsesLocalOAuth(provider: string): boolean;
|
|
|
161
217
|
export declare function renderMarkdownLines(text: string, width: number, color: boolean): string[];
|
|
162
218
|
/** Cut one line to fit a width, appending an ellipsis when truncated. */
|
|
163
219
|
export declare function truncateToWidth(text: string, width: number): string;
|
|
220
|
+
/**
|
|
221
|
+
* Clip an already-styled ANSI line to `width` terminal cells without dropping
|
|
222
|
+
* the reset/SGR sequences. Used by the incremental painter so a leftover wide
|
|
223
|
+
* glyph cannot wrap into the next row.
|
|
224
|
+
*/
|
|
225
|
+
export declare function clipAnsiToWidth(text: string, width: number): string;
|
|
164
226
|
/** One renderable view of the input line: text plus the cursor's visual offset. */
|
|
165
227
|
interface InputView {
|
|
166
228
|
text: string;
|
|
@@ -192,6 +254,51 @@ export declare function openCodeSourceFor(provider: string, llmPiAiSection: unkn
|
|
|
192
254
|
export declare function formatOpenCodeGoUsage(payload: unknown, source: OpenCodeSource): string;
|
|
193
255
|
/** Whether `text` could still grow into a recognized escape sequence. */
|
|
194
256
|
export declare function isEscapePrefix(text: string): boolean;
|
|
257
|
+
/** True while a plan still belongs in the dock (latest incomplete work). */
|
|
258
|
+
export declare function planIsLive(plan: {
|
|
259
|
+
active: boolean;
|
|
260
|
+
pending: boolean;
|
|
261
|
+
todos: readonly PlanTodoItem[];
|
|
262
|
+
planMarkdown?: string;
|
|
263
|
+
archived?: boolean;
|
|
264
|
+
}): boolean;
|
|
265
|
+
/** Open todos left behind when a turn ends without a completing todo_write. */
|
|
266
|
+
export declare function planTurnLeftOpen(plan: {
|
|
267
|
+
todos: readonly PlanTodoItem[];
|
|
268
|
+
}): boolean;
|
|
269
|
+
/** Mark leftover in-progress/pending todos as display-stale after turn/end. */
|
|
270
|
+
export declare function applyTurnEndToPlan<T extends {
|
|
271
|
+
todos: PlanTodoItem[];
|
|
272
|
+
turnLeftOpen?: boolean;
|
|
273
|
+
}>(plan: T): T;
|
|
274
|
+
/** Follow-up that asks the model to close leftover todos. One per open list. */
|
|
275
|
+
export declare function planCloseNudgeText(plan: {
|
|
276
|
+
todos: readonly PlanTodoItem[];
|
|
277
|
+
}): string;
|
|
278
|
+
export type CardCategory = 'thinking' | 'plan' | 'subagent' | 'reply' | 'tool' | 'question' | 'goal';
|
|
279
|
+
/** Category for jump / search. Assistant replies are not collapsible cards. */
|
|
280
|
+
export declare function cardCategoryOf(row: {
|
|
281
|
+
kind: string;
|
|
282
|
+
}): CardCategory | undefined;
|
|
283
|
+
/** Split `/find thinking padAnsi` into an optional category and a query. */
|
|
284
|
+
export declare function parseFindQuery(raw: string): {
|
|
285
|
+
category?: CardCategory;
|
|
286
|
+
query: string;
|
|
287
|
+
};
|
|
288
|
+
/** Transcript rows matching a `/find` query, newest last. */
|
|
289
|
+
export declare function matchTranscriptRows(rows: readonly Row[], raw: string): Row[];
|
|
290
|
+
/** One-line note under an expanded plan strip. */
|
|
291
|
+
export declare function planDockNote(plan: {
|
|
292
|
+
active: boolean;
|
|
293
|
+
pending: boolean;
|
|
294
|
+
todos: readonly PlanTodoItem[];
|
|
295
|
+
planMarkdown?: string;
|
|
296
|
+
turnLeftOpen?: boolean;
|
|
297
|
+
}): string;
|
|
298
|
+
/** Compact per-status counts matching the web plan strip. */
|
|
299
|
+
export declare function todoProgressLabel(todos: readonly PlanTodoItem[]): string;
|
|
300
|
+
/** First markdown heading of an exit_plan_mode plan body. */
|
|
301
|
+
export declare function planTitleFromMarkdown(markdown: string): string | undefined;
|
|
195
302
|
/** Parse a todo_write payload into displayable plan items. */
|
|
196
303
|
export declare function parsePlanTodos(value: unknown): PlanTodoItem[];
|
|
197
304
|
/** Compact todo-list summary: done/total plus the first in-progress task. */
|
|
@@ -223,6 +330,7 @@ export declare function renderToolDiff(diffs: ToolDiffHunk[], maxLines: number):
|
|
|
223
330
|
export declare function friendlyJsonLines(value: unknown, depth?: number): string[];
|
|
224
331
|
/** Minimal tool-row shape the expanded-body renderer reads. */
|
|
225
332
|
interface ToolBodySource {
|
|
333
|
+
name?: string;
|
|
226
334
|
diff?: ToolDiffHunk[];
|
|
227
335
|
command?: string;
|
|
228
336
|
status?: 'running' | 'ok' | 'error';
|
|
@@ -313,6 +421,14 @@ export declare class SshTui {
|
|
|
313
421
|
private lastTitleUpdateAt;
|
|
314
422
|
private lastPaintRows;
|
|
315
423
|
private lastChromeKey;
|
|
424
|
+
private lastPaintWidth;
|
|
425
|
+
private lastPaintHeight;
|
|
426
|
+
private readonly paintIntervalMs;
|
|
427
|
+
private searchHits;
|
|
428
|
+
private searchIndex;
|
|
429
|
+
private searchQuery;
|
|
430
|
+
private planNudgePending;
|
|
431
|
+
private pendingReveal;
|
|
316
432
|
constructor(ctx: Context, agent: Agent, config: TuiConfig);
|
|
317
433
|
/** Enter raw mode, switch to the alternate screen, and start listening. */
|
|
318
434
|
start(): void;
|
|
@@ -327,6 +443,8 @@ export declare class SshTui {
|
|
|
327
443
|
dispose(): Promise<void>;
|
|
328
444
|
/** Human-facing exit with goodbye and flush; called from key handling. */
|
|
329
445
|
requestExit(code: number): Promise<void>;
|
|
446
|
+
/** Capture one painted frame. Used by README screenshot fixtures. */
|
|
447
|
+
captureFrame(columns?: number, rows?: number): string[];
|
|
330
448
|
private write;
|
|
331
449
|
private markDirty;
|
|
332
450
|
/** Append one transcript row, bounding memory on long sessions. */
|
|
@@ -336,7 +454,15 @@ export declare class SshTui {
|
|
|
336
454
|
private spinnerFrame;
|
|
337
455
|
private findSubagentRow;
|
|
338
456
|
private findLivePlanRow;
|
|
457
|
+
/** Older / finished plans stay in the scrolling transcript. */
|
|
458
|
+
private archiveStalePlans;
|
|
339
459
|
private upsertPlanRow;
|
|
460
|
+
/** Whether the live plan strip should occupy the workspace footer. */
|
|
461
|
+
private shouldDockPlan;
|
|
462
|
+
/** One follow-up per leftover list; replay and cancelled turns stay quiet. */
|
|
463
|
+
private queuePlanCloseNudge;
|
|
464
|
+
/** Compact web-style plan strip pinned above the input, not in the transcript. */
|
|
465
|
+
private paintPlanDock;
|
|
340
466
|
private paintCollapsibleHeader;
|
|
341
467
|
/** Move the expand/collapse focus among reasoning and tool rows. */
|
|
342
468
|
private moveCollapsibleFocus;
|
|
@@ -344,6 +470,14 @@ export declare class SshTui {
|
|
|
344
470
|
private toggleCollapsible;
|
|
345
471
|
/** Expand all collapsible blocks, or collapse them again when all are open. */
|
|
346
472
|
private toggleAllCollapsible;
|
|
473
|
+
private highlightSearchLine;
|
|
474
|
+
private revealRow;
|
|
475
|
+
private focusCard;
|
|
476
|
+
/** Jump to the newest card in a category (thinking / plan / subagent / reply). */
|
|
477
|
+
private jumpToCategory;
|
|
478
|
+
private applySearchHits;
|
|
479
|
+
private runFindCommand;
|
|
480
|
+
private stepSearch;
|
|
347
481
|
private paint;
|
|
348
482
|
private buildSuggestions;
|
|
349
483
|
private suggestionsVisible;
|
|
@@ -422,10 +556,20 @@ export declare class SshTui {
|
|
|
422
556
|
private pickModelOption;
|
|
423
557
|
/** Live adapter routes the TUI can switch to, plus the current selection. */
|
|
424
558
|
private listSelectableProviders;
|
|
425
|
-
/**
|
|
559
|
+
/** Built-in SuperGrok catalog used when the live adapter list is still warming up. */
|
|
560
|
+
private static readonly XAI_FALLBACK_MODELS;
|
|
561
|
+
/** /model: stay on the current provider by default; switching providers is opt-in. */
|
|
426
562
|
private runModelCommand;
|
|
563
|
+
/** Persist a provider/model/effort choice and keep the subagent on the same family. */
|
|
564
|
+
private applyModelSelection;
|
|
427
565
|
/** Provider route the next subagent request should use. */
|
|
428
566
|
private effectiveSubagentProvider;
|
|
567
|
+
/**
|
|
568
|
+
* When the parent provider changes (OAuth or API key), keep the subagent
|
|
569
|
+
* on a same-family model. An explicit leftover DeepSeek flash id after
|
|
570
|
+
* switching to xAI is treated as stale.
|
|
571
|
+
*/
|
|
572
|
+
private syncSubagentToProvider;
|
|
429
573
|
/** Persist one subagent selection and publish it to the live request waterfall. */
|
|
430
574
|
private saveSubagentSelection;
|
|
431
575
|
/** Resolve the picker model list for one provider (endpoint first, then catalog). */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-ssh-tui",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "SSH-friendly interactive terminal TUI plugin for DeepSeek Harness",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
@@ -42,6 +42,9 @@
|
|
|
42
42
|
"cordis.patch.yml",
|
|
43
43
|
"README.md",
|
|
44
44
|
"README.en.md",
|
|
45
|
+
"docs/screenshots/compare.png",
|
|
46
|
+
"docs/screenshots/headless.png",
|
|
47
|
+
"docs/screenshots/workspace.png",
|
|
45
48
|
"LICENSE"
|
|
46
49
|
],
|
|
47
50
|
"license": "MIT",
|
|
@@ -61,7 +64,8 @@
|
|
|
61
64
|
"install:dsh": "bash scripts/install.sh",
|
|
62
65
|
"install:npm": "bash scripts/install-npm.sh",
|
|
63
66
|
"uninstall:dsh": "bash scripts/uninstall.sh",
|
|
64
|
-
"verify:dsh": "bash scripts/verify.sh"
|
|
67
|
+
"verify:dsh": "bash scripts/verify.sh",
|
|
68
|
+
"screenshots": "npm run build && node scripts/capture-readme-frames.mjs"
|
|
65
69
|
},
|
|
66
70
|
"engines": {
|
|
67
71
|
"node": ">=22.19"
|