dsh-ssh-tui 0.5.7 → 0.5.9
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 +25 -0
- package/README.md +45 -0
- package/lib/display-sock.js +234 -62
- package/lib/display-sock.js.map +1 -1
- package/lib/dsh-compat.js +138 -5
- package/lib/dsh-compat.js.map +1 -1
- package/lib/footer.js +27 -2
- package/lib/footer.js.map +1 -1
- package/lib/i18n/en.js +1 -0
- package/lib/i18n/en.js.map +1 -1
- package/lib/i18n/zh.js +1 -0
- package/lib/i18n/zh.js.map +1 -1
- package/lib/index.js +59 -10
- package/lib/index.js.map +1 -1
- package/lib/paint.js +21 -2
- package/lib/paint.js.map +1 -1
- package/lib/picker.js +59 -28
- package/lib/picker.js.map +1 -1
- package/lib/session-index.js +2 -2
- package/lib/session-index.js.map +1 -1
- package/lib/session-lock.js +181 -25
- package/lib/session-lock.js.map +1 -1
- package/lib/tui.js +103 -24
- package/lib/tui.js.map +1 -1
- package/lib/types/display-sock.d.ts +62 -5
- package/lib/types/dsh-compat.d.ts +60 -2
- package/lib/types/footer.d.ts +11 -1
- package/lib/types/index.d.ts +4 -0
- package/lib/types/paint.d.ts +13 -0
- package/lib/types/picker.d.ts +8 -2
- package/lib/types/session-lock.d.ts +42 -3
- package/lib/types/tui.d.ts +17 -5
- package/package.json +3 -3
|
@@ -12,7 +12,40 @@ export declare const FRAME_RTT = 6;
|
|
|
12
12
|
export declare function detachFromSshSession(): void;
|
|
13
13
|
export declare const TUI_HOST_ENV = "DSH_TUI_HOST";
|
|
14
14
|
export declare function isTuiHostProcess(env?: NodeJS.ProcessEnv): boolean;
|
|
15
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Windows named-pipe namespace. Node's `net` passes the string straight to
|
|
17
|
+
* CreateNamedPipeW/CreateFileW, and those require this prefix — a drive-letter
|
|
18
|
+
* path fails with ENOENT/EACCES and the Host never listens.
|
|
19
|
+
*/
|
|
20
|
+
export declare const WINDOWS_PIPE_PREFIX = "\\\\.\\pipe\\";
|
|
21
|
+
/** True for a Windows named-pipe address (`\\.\pipe\x`, `\\?\pipe\x`, `//./pipe/x`). */
|
|
22
|
+
export declare function isPipePath(path: string): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* `DSH_HOME` the way dsh itself resolves it: a blank value counts as unset, and
|
|
25
|
+
* the result is absolute. Both matter here because the launcher and the
|
|
26
|
+
* detached Host compute channel names independently and the Host chdirs into
|
|
27
|
+
* the session's working directory first — a relative or empty home would give
|
|
28
|
+
* them two different sockets (or pipe names) and time the launch out.
|
|
29
|
+
*/
|
|
30
|
+
export declare function resolveDshHome(env?: NodeJS.ProcessEnv, home?: string): string;
|
|
31
|
+
/** Directory holding per-session display runtime state (error logs). */
|
|
32
|
+
export declare function sessionSockDir(dshHome?: string): string;
|
|
33
|
+
/** Filesystem/pipe-safe form of a session id, as used for locks and channels. */
|
|
34
|
+
export declare function safeSessionId(sessionId: string): string;
|
|
35
|
+
/**
|
|
36
|
+
* Address of the per-session display channel: a filesystem path on POSIX, a
|
|
37
|
+
* named pipe on Windows. `platform` is injectable so the Windows shape stays
|
|
38
|
+
* testable from a POSIX test run.
|
|
39
|
+
*/
|
|
40
|
+
export declare function sessionSockPath(sessionId: string, dshHome?: string, platform?: NodeJS.Platform): string;
|
|
41
|
+
/**
|
|
42
|
+
* Host stderr log for one session. POSIX keeps the historical `<sock>.err`
|
|
43
|
+
* next to the socket; a Windows pipe name is not a file path, so the log lives
|
|
44
|
+
* in the `tui-socks` state directory instead — under the same digested label,
|
|
45
|
+
* which also keeps long ids inside the Windows path limit and keeps reserved
|
|
46
|
+
* device names (`CON`, `NUL`, …) from becoming the file stem.
|
|
47
|
+
*/
|
|
48
|
+
export declare function sessionErrPath(sessionId: string, dshHome?: string, platform?: NodeJS.Platform): string;
|
|
16
49
|
export declare function encodeFrame(type: number, payload?: Buffer): Buffer;
|
|
17
50
|
export declare function encodeResize(columns: number, rows: number): Buffer;
|
|
18
51
|
export declare function decodeResize(payload: Buffer): {
|
|
@@ -56,14 +89,38 @@ export declare class DisplayHost {
|
|
|
56
89
|
sendGoodbye(): void;
|
|
57
90
|
close(): Promise<void>;
|
|
58
91
|
}
|
|
59
|
-
|
|
92
|
+
/**
|
|
93
|
+
* True once a Host is actually accepting on the display channel.
|
|
94
|
+
*
|
|
95
|
+
* Always a real connect, never a filesystem check: a leftover `.sock` file
|
|
96
|
+
* (killed Host, or one that is mid-dispose) is a directory entry, not a peer,
|
|
97
|
+
* and `fs.access()` used to report it as ready — the launcher then wrote HELLO
|
|
98
|
+
* into a dead socket and the first reconnect after a drop died with
|
|
99
|
+
* `write EPIPE`. Windows pipes additionally cannot be seen by `fs.access` at
|
|
100
|
+
* all. The Host treats a connect without HELLO as a liveness probe and drops
|
|
101
|
+
* it without stealing the display.
|
|
102
|
+
*/
|
|
103
|
+
export declare function displaySockExists(path: string, timeoutMs?: number): Promise<boolean>;
|
|
104
|
+
/** Watches a freshly spawned Host so a crash is reported immediately. */
|
|
105
|
+
export interface HostExitWatch {
|
|
106
|
+
/** Resolves with the exit code (or null when killed) once the Host exits. */
|
|
107
|
+
readonly exited: Promise<number | null>;
|
|
108
|
+
/** Stop watching; call once the channel is confirmed up. */
|
|
109
|
+
dispose(): void;
|
|
110
|
+
}
|
|
111
|
+
export declare function waitForDisplaySock(path: string, timeoutMs?: number, pid?: number, errFile?: string, exitWatch?: HostExitWatch): Promise<void>;
|
|
60
112
|
export declare function hostArgvForSession(sessionId: string, argv?: string[], execArgv?: string[]): string[];
|
|
61
|
-
|
|
62
|
-
export declare function spawnDetachedHost(sessionId: string): {
|
|
113
|
+
export interface SpawnedHost {
|
|
63
114
|
pid: number;
|
|
115
|
+
/** Channel address: a socket file on POSIX, a named pipe on Windows. */
|
|
64
116
|
sock: string;
|
|
117
|
+
/** Host stderr log; present when it could be opened. */
|
|
65
118
|
errFile?: string;
|
|
66
|
-
|
|
119
|
+
/** Exit watch so a Host that dies before listening is reported at once. */
|
|
120
|
+
exitWatch: HostExitWatch;
|
|
121
|
+
}
|
|
122
|
+
/** Spawn a detached Host copy of this `dsh` invocation and return its sock path. */
|
|
123
|
+
export declare function spawnDetachedHost(sessionId: string): SpawnedHost;
|
|
67
124
|
export declare function probeDisplaySock(path: string, timeoutMs?: number): Promise<boolean>;
|
|
68
125
|
export interface RelayResult {
|
|
69
126
|
/** Host sent goodbye — user exited from the attached session. */
|
|
@@ -37,6 +37,18 @@ export declare function sessionEvents(session: object): readonly SessionEvent[];
|
|
|
37
37
|
* not materialize a second array of every chunk.
|
|
38
38
|
*/
|
|
39
39
|
export declare function forEachSessionEvent(session: object, visit: (event: SessionEvent) => void): void;
|
|
40
|
+
/** Events folded between event-loop turns while a long log is replayed. */
|
|
41
|
+
export declare const REPLAY_YIELD_EVERY = 200;
|
|
42
|
+
/**
|
|
43
|
+
* {@link forEachSessionEvent} for the resume replay. A synchronous walk of a
|
|
44
|
+
* large log blocks the loop for its whole duration, which freezes the TUI on
|
|
45
|
+
* the pre-replay frame: the relay's RTT frame and the render timer cannot run,
|
|
46
|
+
* so the footer keeps painting `SSH ○○○○` and the transcript never fills in
|
|
47
|
+
* until the walk ends. Yielding every few hundred events keeps both flowing.
|
|
48
|
+
*
|
|
49
|
+
* @param halt - stop early when the TUI was disposed mid-replay.
|
|
50
|
+
*/
|
|
51
|
+
export declare function forEachSessionEventAsync(session: object, visit: (event: SessionEvent) => void, yieldEvery?: number, halt?: () => boolean): Promise<void>;
|
|
40
52
|
/** Header fields the picker and resume path actually read. */
|
|
41
53
|
export interface SessionHeaderLike {
|
|
42
54
|
id: string;
|
|
@@ -76,17 +88,63 @@ export declare function commandAcceptsAttachments(input: unknown): boolean;
|
|
|
76
88
|
export interface StreamChunkLike {
|
|
77
89
|
type: string;
|
|
78
90
|
text?: string;
|
|
91
|
+
/** `tool-call-delta` argument fragment. */
|
|
92
|
+
argumentsDelta?: string;
|
|
93
|
+
/** `tool-call-delta` tool name, present on the first fragment. */
|
|
94
|
+
name?: string;
|
|
79
95
|
usage?: {
|
|
80
96
|
inputTokens?: number;
|
|
81
97
|
outputTokens?: number;
|
|
82
98
|
};
|
|
83
99
|
}
|
|
84
|
-
/**
|
|
85
|
-
|
|
100
|
+
/**
|
|
101
|
+
* Whether one chunk carries the model's first output token, matching the host's
|
|
102
|
+
* own `isTokenDelta`: a non-empty text/reasoning fragment, or a tool-call
|
|
103
|
+
* fragment (name-bearing deltas included). Thinking-first models therefore
|
|
104
|
+
* start the latency clock on their first reasoning token, not on the first
|
|
105
|
+
* visible answer token.
|
|
106
|
+
*/
|
|
107
|
+
export declare function isTokenDeltaChunk(chunk: unknown): boolean;
|
|
108
|
+
/**
|
|
109
|
+
* Turn/step of a live `agent/assistant-stream` `start` frame. The chunk frames
|
|
110
|
+
* this opens carry no turn/step at all, so callers must remember them or every
|
|
111
|
+
* live chunk is attributed to step 0 (which silently disabled TTFT, decode
|
|
112
|
+
* speed, and usage de-duplication).
|
|
113
|
+
*/
|
|
114
|
+
export declare function streamFrameOwner(frame: unknown): {
|
|
115
|
+
attemptId: unknown;
|
|
116
|
+
turn: number;
|
|
117
|
+
step: number;
|
|
118
|
+
} | undefined;
|
|
119
|
+
/** Attempt id of a live chunk/end frame, used to match it to its `start`. */
|
|
120
|
+
export declare function streamFrameAttemptId(frame: unknown): unknown;
|
|
121
|
+
/**
|
|
122
|
+
* First token time inside a durable compact assistant stream
|
|
123
|
+
* (`assistant/message.stream`). Mirrors the host's
|
|
124
|
+
* `assistantStreamFirstTokenTime`: packed delta runs place member `i` at
|
|
125
|
+
* `time0 + dt[0..i-1]`, raw chunk records carry their own time.
|
|
126
|
+
*/
|
|
127
|
+
export declare function streamFirstTokenTime(stream: unknown): number | undefined;
|
|
128
|
+
/**
|
|
129
|
+
* Durable `assistant/chunk` payload, or a live stream frame's inner chunk.
|
|
130
|
+
*
|
|
131
|
+
* `fallback` supplies the turn/step for live chunk frames, which do not carry
|
|
132
|
+
* them (see {@link streamFrameOwner}).
|
|
133
|
+
*/
|
|
134
|
+
export declare function streamChunkOf(eventOrFrame: unknown, fallback?: {
|
|
135
|
+
turn: number;
|
|
136
|
+
step: number;
|
|
137
|
+
}): {
|
|
86
138
|
chunk: StreamChunkLike;
|
|
87
139
|
turn: number;
|
|
88
140
|
step: number;
|
|
89
141
|
time: number;
|
|
142
|
+
/**
|
|
143
|
+
* False when neither the source nor a fallback carried a real turn/step.
|
|
144
|
+
* Usage folded under such a chunk would be filed under a bogus key (0:0)
|
|
145
|
+
* that `step/end` never clears, inflating the session totals forever.
|
|
146
|
+
*/
|
|
147
|
+
stepKnown: boolean;
|
|
90
148
|
} | undefined;
|
|
91
149
|
/** Durable event type as a plain string so 0.1.5 hosts can omit `assistant/chunk`. */
|
|
92
150
|
export declare function sessionEventType(event: unknown): string;
|
package/lib/types/footer.d.ts
CHANGED
|
@@ -94,7 +94,10 @@ export interface FooterStatusInput {
|
|
|
94
94
|
provider: string;
|
|
95
95
|
parentModel: string;
|
|
96
96
|
subModel: string;
|
|
97
|
-
|
|
97
|
+
/** Explicit `/submodel` provider override; undefined means "inherit parent". */
|
|
98
|
+
subProvider?: string;
|
|
99
|
+
/** Explicit `/subeffort` override for subagent children. */
|
|
100
|
+
subEffort?: string;
|
|
98
101
|
quotaCode?: string;
|
|
99
102
|
quotaPercent?: number;
|
|
100
103
|
contextChip?: string;
|
|
@@ -115,6 +118,13 @@ export declare function footerActivity(input: FooterStatusInput): {
|
|
|
115
118
|
};
|
|
116
119
|
/** Short remaining-quota bar: 8 pips, filled from the left. */
|
|
117
120
|
export declare function formatQuotaBar(remainingPercent: number, width?: number): string;
|
|
121
|
+
/**
|
|
122
|
+
* `sub:<model>` chip for the identity row, mirroring the pre-0.3.6 footer:
|
|
123
|
+
* an explicit `/submodel` provider is prefixed (`sub:xai/grok-4.5`) and an
|
|
124
|
+
* explicit `/subeffort` is appended in parentheses (`sub:grok-4.5(xhigh)`).
|
|
125
|
+
* An inherited provider stays implicit — it is the parent's route.
|
|
126
|
+
*/
|
|
127
|
+
export declare function subagentRouteLabel(model: string, provider?: string, effort?: string): string;
|
|
118
128
|
export declare function footerIdentityParts(input: FooterStatusInput): string[];
|
|
119
129
|
/** `SuperGrok ███████░ 82%`, or just the bar + percent when `code` is omitted. */
|
|
120
130
|
export declare function formatFooterQuota(percent: number, code?: string): string;
|
package/lib/types/index.d.ts
CHANGED
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
* subagents, sandbox approvals) is the same one the web surface uses.
|
|
6
6
|
*/
|
|
7
7
|
import type { Context } from '@deepseek-ai/cordis';
|
|
8
|
+
/** A relay that dies this soon after connecting reached a Host that was leaving. */
|
|
9
|
+
export declare const ATTACH_RECOVERY_WINDOW_MS = 5000;
|
|
10
|
+
/** True when a relay error means the peer vanished rather than a real fault. */
|
|
11
|
+
export declare function attachPeerVanished(error: unknown, elapsedMs: number): boolean;
|
|
8
12
|
export declare const name = "ssh-tui";
|
|
9
13
|
/** Core services required before the terminal channel can drive an agent. */
|
|
10
14
|
export declare const inject: string[];
|
package/lib/types/paint.d.ts
CHANGED
|
@@ -68,6 +68,19 @@ export declare function parseCursorPositionReply(text: string): {
|
|
|
68
68
|
row: number;
|
|
69
69
|
column: number;
|
|
70
70
|
} | undefined;
|
|
71
|
+
/**
|
|
72
|
+
* Find a cursor reply inside a noisy buffer.
|
|
73
|
+
*
|
|
74
|
+
* The anchored parse above only works when the CPR is the whole chunk. Real
|
|
75
|
+
* terminals interleave it with focus events, mouse reports, bracketed-paste
|
|
76
|
+
* marks or keystrokes that raced the probe, and an anchored match then fails
|
|
77
|
+
* until the probe times out — which is how the footer's link chip ends up
|
|
78
|
+
* stuck on four hollow circles (`SSH ○○○○`) for a whole session.
|
|
79
|
+
*/
|
|
80
|
+
export declare function findCursorPositionReply(text: string): {
|
|
81
|
+
row: number;
|
|
82
|
+
column: number;
|
|
83
|
+
} | undefined;
|
|
71
84
|
/**
|
|
72
85
|
* Round-trip to the attached terminal via CSI 6n. Returns undefined when the
|
|
73
86
|
* reply never arrives (dumb pipe, blocked DSR). Does not interpret the
|
package/lib/types/picker.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* typing filters by title / id / cwd, and Enter confirms the focused row.
|
|
9
9
|
*/
|
|
10
10
|
import type { Context } from '@deepseek-ai/cordis';
|
|
11
|
-
import { type ResumableSession } from './session-list.js';
|
|
11
|
+
import { listResumableSessionsProgressive, type ResumableSession } from './session-list.js';
|
|
12
12
|
/** What the launch picker decided. */
|
|
13
13
|
export type SessionPickerResult = {
|
|
14
14
|
kind: 'resume';
|
|
@@ -119,4 +119,10 @@ export declare function feedPicker(state: SessionPickerState, text: string, wind
|
|
|
119
119
|
* @param signal - optional abort signal (fiber dispose) to cancel the picker.
|
|
120
120
|
* @returns the selection, or null when cancelled.
|
|
121
121
|
*/
|
|
122
|
-
|
|
122
|
+
/** Injection seams for tests; production uses the TTY and the session index. */
|
|
123
|
+
export interface SessionPickerOptions {
|
|
124
|
+
stdin?: NodeJS.ReadStream;
|
|
125
|
+
stdout?: NodeJS.WriteStream;
|
|
126
|
+
listSessions?: typeof listResumableSessionsProgressive;
|
|
127
|
+
}
|
|
128
|
+
export declare function showSessionPicker(ctx: Context, color: boolean, signal?: AbortSignal, options?: SessionPickerOptions): Promise<SessionPickerResult>;
|
|
@@ -24,6 +24,38 @@ export declare function sessionLockPath(sessionId: string, dshHome?: string): st
|
|
|
24
24
|
export declare function parseSessionLock(raw: string): SessionLockInfo | undefined;
|
|
25
25
|
/** True when `pid` still exists on this machine (best-effort). */
|
|
26
26
|
export declare function processIsAlive(pid: number): boolean;
|
|
27
|
+
/** Slice of a Windows process identity needed to rule out pid reuse. */
|
|
28
|
+
export interface WindowsProcessIdentity {
|
|
29
|
+
/** Executable name without `.exe`, lowercased (`node`). */
|
|
30
|
+
name: string;
|
|
31
|
+
/** Process creation time in epoch milliseconds, when readable. */
|
|
32
|
+
startedAt?: number;
|
|
33
|
+
}
|
|
34
|
+
/** Clock/resolution slack when comparing a creation time with the lock write. */
|
|
35
|
+
export declare const PID_REUSE_SLACK_MS = 5000;
|
|
36
|
+
/**
|
|
37
|
+
* Parse the probe's stdout. Everything after the last non-empty line is
|
|
38
|
+
* ignored (a stray warning banner must not masquerade as the image name), and
|
|
39
|
+
* an unexpected shape is reported as "unverifiable" rather than as a
|
|
40
|
+
* mismatch: mistaking noise for a foreign process would steal a live lock.
|
|
41
|
+
*/
|
|
42
|
+
export declare function parseWindowsProcessIdentity(stdout: string): WindowsProcessIdentity | undefined;
|
|
43
|
+
/** Image name this Host runs as (`node` / `dsh`), without `.exe`. */
|
|
44
|
+
export declare function hostImageName(execPath?: string): string;
|
|
45
|
+
/**
|
|
46
|
+
* Decide whether a live Windows pid can still be the lock's Host.
|
|
47
|
+
*
|
|
48
|
+
* Windows recycles pids aggressively, and without procfs an unrelated process
|
|
49
|
+
* inheriting the recorded pid used to look like a permanent live-but-silent
|
|
50
|
+
* Host ("zombie"), which blocked `--resume` until the lock was deleted by
|
|
51
|
+
* hand. Two facts rule reuse out:
|
|
52
|
+
* - the Host always runs this same executable, so a different image name is
|
|
53
|
+
* someone else's process;
|
|
54
|
+
* - the Host existed before it wrote the lock, so a process created after the
|
|
55
|
+
* lock was written cannot be its owner.
|
|
56
|
+
* An unverifiable process keeps the legacy best-effort answer (alive).
|
|
57
|
+
*/
|
|
58
|
+
export declare function windowsProcessMatchesLock(lock: SessionLockInfo, identity: WindowsProcessIdentity | undefined, expectedName?: string, slackMs?: number): boolean;
|
|
27
59
|
/**
|
|
28
60
|
* True when `pid` is genuinely the Host process that wrote `lock`.
|
|
29
61
|
*
|
|
@@ -38,10 +70,17 @@ export declare function processIsAlive(pid: number): boolean;
|
|
|
38
70
|
* boot, so a recycled or cross-namespace pid fails the check;
|
|
39
71
|
* - older locks fall back to `/proc/<pid>/cmdline`: the detached Host is
|
|
40
72
|
* always launched with `--resume=<sessionId>` in argv, so any other
|
|
41
|
-
* process (kernel threads have an empty cmdline) is proven stale
|
|
42
|
-
*
|
|
73
|
+
* process (kernel threads have an empty cmdline) is proven stale;
|
|
74
|
+
* - Windows has neither: a `Get-Process` probe supplies the image name and
|
|
75
|
+
* creation time instead (see {@link windowsProcessMatchesLock}).
|
|
76
|
+
* On platforms where none of this is available the legacy kill(pid, 0)
|
|
77
|
+
* behavior is kept.
|
|
78
|
+
*
|
|
79
|
+
* Async because the Windows probe spawns PowerShell, and this runs on the
|
|
80
|
+
* render path (`/resume` inspects every lock); a synchronous spawn would
|
|
81
|
+
* freeze painting and keystrokes for the duration of the probe.
|
|
43
82
|
*/
|
|
44
|
-
export declare function lockOwnerIsAlive(lock: SessionLockInfo): boolean
|
|
83
|
+
export declare function lockOwnerIsAlive(lock: SessionLockInfo): Promise<boolean>;
|
|
45
84
|
export declare function formatLockHeldMessage(lock: SessionLockInfo): string;
|
|
46
85
|
export declare function sessionLockDisabled(env?: NodeJS.ProcessEnv): boolean;
|
|
47
86
|
export declare function readSessionLock(sessionId: string, dshHome?: string): Promise<{
|
package/lib/types/tui.d.ts
CHANGED
|
@@ -27,10 +27,10 @@ import type { CollapsibleBlock, DisconnectPolicyName } from './transcript-types.
|
|
|
27
27
|
import { paintedLinkHits } from './term-text.js';
|
|
28
28
|
export type { CollapsibleBlock, DisplayKind, DisconnectPolicyName, PlanTodoItem, Row, SubagentLogEntry, ToolDiffHunk, } from './transcript-types.js';
|
|
29
29
|
export { clipAnsiToWidth, cursorVisualPosition, displayWidth, foldInputView, hrefAtColumn, osc52Clipboard, osc8Enabled, paintedLinkHits, fmtElapsedCompact, padAnsiToWidth, padToWidth, renderMarkdownLines, repeatToWidth, shimmerText, truncateToWidth, visibleWidth, waitCardCopy, waitSummaryFromReasoning, wrapWaitDetails, } from './term-text.js';
|
|
30
|
-
export { captureHangupSignals, composePaintOutput, detectSshSession, formatLinkQualityChip, ignoreFurtherHangupSignals, isEscapePrefix, isHangupErrno, linkQualityOf, linkSignalPips, paintIntervalForRtt, paintLinkLabel, parseCursorPositionReply, pickerWindowStart, probeTerminalRttMs, releaseHangupSignals, resolvePaintIntervalMs, waitUntilIdleOrTimeout, writeBootSplash, type LinkQuality, type PaintLinkKind, } from './paint.js';
|
|
31
|
-
export { CONTEXT_IDLE_COMPACT_RATIO, CONTEXT_PRESSURE_DANGER_RATIO, CONTEXT_PRESSURE_WARN_RATIO, CONTEXT_RING_EMPTY, CONTEXT_RING_SEGMENTS, contextPressureAlertText, contextPressureRingColor, contextPressureUsedTokens, contextPressureView, describeProviderRoute, dropFooterQuotaPlanName, fitFooterStatsLine, fitFooterStatusLine, footerActivity, footerIdentityParts, footerStatsGroups, formatContextPressureChip, formatContextPressureRing, formatContextPressureStatusLine, formatDuration, formatFooterQuota, formatQuotaBar, formatStatusReport, formatTokens, formatTokensPerSecond, parseContextPressure, promptPressureTokens, providerShortCode, providerUsesLocalOAuth, shouldIdleAutoCompact, type ContextPressureSample, type ContextPressureView, type FooterActivityKind, type FooterStatsInput, type FooterStatusInput, type StatusReportInput, } from './footer.js';
|
|
30
|
+
export { captureHangupSignals, composePaintOutput, detectSshSession, formatLinkQualityChip, ignoreFurtherHangupSignals, isEscapePrefix, findCursorPositionReply, isHangupErrno, linkQualityOf, linkSignalPips, paintIntervalForRtt, paintLinkLabel, parseCursorPositionReply, pickerWindowStart, probeTerminalRttMs, releaseHangupSignals, resolvePaintIntervalMs, waitUntilIdleOrTimeout, writeBootSplash, type LinkQuality, type PaintLinkKind, } from './paint.js';
|
|
31
|
+
export { CONTEXT_IDLE_COMPACT_RATIO, CONTEXT_PRESSURE_DANGER_RATIO, CONTEXT_PRESSURE_WARN_RATIO, CONTEXT_RING_EMPTY, CONTEXT_RING_SEGMENTS, contextPressureAlertText, contextPressureRingColor, contextPressureUsedTokens, contextPressureView, describeProviderRoute, dropFooterQuotaPlanName, fitFooterStatsLine, fitFooterStatusLine, footerActivity, footerIdentityParts, footerStatsGroups, formatContextPressureChip, formatContextPressureRing, formatContextPressureStatusLine, formatDuration, formatFooterQuota, formatQuotaBar, formatStatusReport, formatTokens, formatTokensPerSecond, parseContextPressure, promptPressureTokens, providerShortCode, providerUsesLocalOAuth, shouldIdleAutoCompact, subagentRouteLabel, type ContextPressureSample, type ContextPressureView, type FooterActivityKind, type FooterStatsInput, type FooterStatusInput, type StatusReportInput, } from './footer.js';
|
|
32
32
|
export { crossedQuotaThresholds, formatAccountBalance, formatFooterBalance, formatOpenCodeGoUsage, formatQuotaSnapshot, formatQuotaStatusLine, joinUrl, openCodeSourceFor, parseDeepSeekBalance, parseOpenAiCompatibleBalance, parseOpenCodeGoQuota, parseSuperGrokBilling, quotaAlertText, quotaRefreshEverySteps, quotaRefreshEveryTurns, remainingPercentFromUsed, tightestQuotaWindow, type AccountBalanceLine, type AccountBalanceSnapshot, type OpenCodeFlavor, type OpenCodeSource, type QuotaPeriod, type QuotaSnapshot, type QuotaWindow, } from './quota.js';
|
|
33
|
-
export { commandAcceptsAttachments, forEachSessionEvent, isAssistantStreamEvent, listPersistenceHeaders, inspectPersistenceSession, sessionEventType, sessionEvents, settingsNamespace, streamChunkOf, } from './dsh-compat.js';
|
|
33
|
+
export { commandAcceptsAttachments, forEachSessionEvent, isAssistantStreamEvent, isTokenDeltaChunk, listPersistenceHeaders, inspectPersistenceSession, sessionEventType, sessionEvents, settingsNamespace, streamChunkOf, streamFirstTokenTime, streamFrameAttemptId, streamFrameOwner, } from './dsh-compat.js';
|
|
34
34
|
export { applyTurnEndToPlan, askSummary, cardCategoryOf, compactionHeaderText, formatCompactCommandError, isPromptInjectionMessage, matchTranscriptRows, parseFindQuery, parsePlanTodos, planCloseNudgeText, planDockNote, planIsLive, planTitleFromMarkdown, planTurnLeftOpen, promptInjectionSources, promptInjectionTitle, subagentHeaderText, todoProgressLabel, todoSummary, type CardCategory, } from './plan.js';
|
|
35
35
|
export { buildToolHeader, canMergeToolCall, compactEditPath, compactToolBursts, compactToolGroups, countDiffAddDel, countDiffLines, countOutputLines, diffMetaDiffs, diffStatToken, friendlyJsonLines, parseExitStatus, presentToolCall, READ_TOOL_NAMES, renderToolDiff, toolBodyFitsWorkspace, toolBodyLines, toolStateColor, toolStateLabel, wrappedToolBodyLineCount, } from './tool-present.js';
|
|
36
36
|
/** Presentation configuration for the terminal channel. */
|
|
@@ -181,6 +181,12 @@ export declare class SshTui {
|
|
|
181
181
|
private toolCallNames;
|
|
182
182
|
private readonly stats;
|
|
183
183
|
private openStepStats;
|
|
184
|
+
/** Attempt whose live `start` frame opened the current token stream. */
|
|
185
|
+
private liveStreamOwner;
|
|
186
|
+
/** Live events parked while the (yielding) history replay holds the floor. */
|
|
187
|
+
private replayQueue;
|
|
188
|
+
/** A relay claimed the display while a hangup was still cancelling/flushing. */
|
|
189
|
+
private reattachedDuringHangup;
|
|
184
190
|
private readonly pendingToolTimes;
|
|
185
191
|
private readonly usageByStep;
|
|
186
192
|
private lastStatsTurn;
|
|
@@ -257,8 +263,13 @@ export declare class SshTui {
|
|
|
257
263
|
private paintCompactSummary;
|
|
258
264
|
private startRenderTimer;
|
|
259
265
|
private calibratePaintInterval;
|
|
260
|
-
/**
|
|
261
|
-
|
|
266
|
+
/**
|
|
267
|
+
* Replay the durable session log so a resumed session renders its history.
|
|
268
|
+
* Chunked on purpose: a synchronous walk of a long log froze the TUI on the
|
|
269
|
+
* pre-replay frame, so the relay's RTT frame could not be applied and the
|
|
270
|
+
* footer sat on `SSH ○○○○` for the whole load.
|
|
271
|
+
*/
|
|
272
|
+
replayHistory(): Promise<void>;
|
|
262
273
|
/** Show the first-launch provider/API-key onboarding when nothing is configured. */
|
|
263
274
|
private maybeRunOnboarding;
|
|
264
275
|
/** Run the provider/API-key onboarding wizard. Resolves true when saved. */
|
|
@@ -409,6 +420,7 @@ export declare class SshTui {
|
|
|
409
420
|
readonly handleSessionEvent: (session: {
|
|
410
421
|
id: SessionId;
|
|
411
422
|
}, event: SessionEvent) => void;
|
|
423
|
+
private applySessionEvent;
|
|
412
424
|
private readonly handleStatus;
|
|
413
425
|
private readonly handleError;
|
|
414
426
|
private readonly handleInboxClaimed;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-ssh-tui",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.9",
|
|
4
4
|
"description": "SSH-friendly interactive terminal TUI plugin for DeepSeek Harness",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
@@ -72,8 +72,8 @@
|
|
|
72
72
|
"scripts": {
|
|
73
73
|
"build": "tsc -p tsconfig.json",
|
|
74
74
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
75
|
-
"test": "npm run build && node --test tests/*.test.mjs",
|
|
76
|
-
"clean": "
|
|
75
|
+
"test": "npm run build && node --test \"tests/*.test.mjs\"",
|
|
76
|
+
"clean": "node -e \"require('node:fs').rmSync('lib', { recursive: true, force: true })\"",
|
|
77
77
|
"prepare": "npm run build",
|
|
78
78
|
"prepack": "npm run build",
|
|
79
79
|
"prepublishOnly": "npm run typecheck",
|