dsh-ssh-tui 0.8.1 → 0.8.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/CHANGELOG.md +158 -0
- package/README.en.md +118 -15
- package/README.md +78 -13
- package/docs/release-notes-0.7.4.md +91 -0
- package/docs/release-notes-0.8.0.md +56 -0
- package/docs/release-notes-0.8.1.md +79 -0
- package/docs/release-notes-0.8.2-rc.1.md +159 -0
- package/docs/release-notes-0.8.2.md +62 -0
- package/docs/screenshots/reconnect.gif +0 -0
- package/docs/terminals.md +23 -0
- package/docs/windows.md +13 -0
- package/lib/approval-state.js +148 -0
- package/lib/approval-state.js.map +1 -0
- package/lib/auth-failure.js +38 -0
- package/lib/auth-failure.js.map +1 -1
- package/lib/color-depth.js +26 -0
- package/lib/color-depth.js.map +1 -1
- package/lib/copy-text.js +30 -8
- package/lib/copy-text.js.map +1 -1
- package/lib/dialogs.js +81 -0
- package/lib/dialogs.js.map +1 -1
- package/lib/display-sock.js +138 -23
- package/lib/display-sock.js.map +1 -1
- package/lib/footer-accents.js +28 -0
- package/lib/footer-accents.js.map +1 -0
- package/lib/footer-budget.js +328 -0
- package/lib/footer-budget.js.map +1 -0
- package/lib/footer-format.js +433 -0
- package/lib/footer-format.js.map +1 -0
- package/lib/footer-layout.js +407 -0
- package/lib/footer-layout.js.map +1 -0
- package/lib/footer-meters.js +185 -0
- package/lib/footer-meters.js.map +1 -0
- package/lib/footer.js +19 -558
- package/lib/footer.js.map +1 -1
- package/lib/i18n/en.js +65 -5
- package/lib/i18n/en.js.map +1 -1
- package/lib/i18n/zh.js +67 -5
- package/lib/i18n/zh.js.map +1 -1
- package/lib/paint.js +53 -8
- package/lib/paint.js.map +1 -1
- package/lib/plan-projection.js +291 -0
- package/lib/plan-projection.js.map +1 -0
- package/lib/plan.js +50 -0
- package/lib/plan.js.map +1 -1
- package/lib/question-state.js +83 -0
- package/lib/question-state.js.map +1 -0
- package/lib/representation.js +283 -0
- package/lib/representation.js.map +1 -0
- package/lib/screen.js +105 -0
- package/lib/screen.js.map +1 -0
- package/lib/selection.js +5 -3
- package/lib/selection.js.map +1 -1
- package/lib/startup.js +49 -0
- package/lib/startup.js.map +1 -1
- package/lib/stats.js +101 -3
- package/lib/stats.js.map +1 -1
- package/lib/term-text.js +154 -55
- package/lib/term-text.js.map +1 -1
- package/lib/theme.js +20 -3
- package/lib/theme.js.map +1 -1
- package/lib/throughput.js +305 -0
- package/lib/throughput.js.map +1 -0
- package/lib/tool-present.js +57 -7
- package/lib/tool-present.js.map +1 -1
- package/lib/tui.js +3776 -1105
- package/lib/tui.js.map +1 -1
- package/lib/types/approval-state.d.ts +75 -0
- package/lib/types/auth-failure.d.ts +25 -0
- package/lib/types/color-depth.d.ts +21 -0
- package/lib/types/copy-text.d.ts +18 -1
- package/lib/types/dialogs.d.ts +111 -1
- package/lib/types/display-sock.d.ts +15 -0
- package/lib/types/footer-accents.d.ts +22 -0
- package/lib/types/footer-budget.d.ts +204 -0
- package/lib/types/footer-format.d.ts +221 -0
- package/lib/types/footer-layout.d.ts +292 -0
- package/lib/types/footer-meters.d.ts +151 -0
- package/lib/types/footer.d.ts +19 -269
- package/lib/types/paint.d.ts +48 -0
- package/lib/types/plan-projection.d.ts +162 -0
- package/lib/types/plan.d.ts +13 -0
- package/lib/types/question-state.d.ts +96 -0
- package/lib/types/representation.d.ts +498 -0
- package/lib/types/screen.d.ts +126 -0
- package/lib/types/startup.d.ts +24 -0
- package/lib/types/stats.d.ts +64 -1
- package/lib/types/term-text.d.ts +45 -1
- package/lib/types/throughput.d.ts +163 -0
- package/lib/types/tool-present.d.ts +41 -0
- package/lib/types/transcript-types.d.ts +63 -2
- package/lib/types/tui.d.ts +862 -11
- package/package.json +16 -6
package/lib/types/stats.d.ts
CHANGED
|
@@ -14,12 +14,41 @@
|
|
|
14
14
|
* - decode rate needs a finite, non-negative output token count.
|
|
15
15
|
*/
|
|
16
16
|
import type { TokenUsage } from '@deepseek-ai/dsh-llm';
|
|
17
|
+
import { type ThroughputView } from './throughput.js';
|
|
17
18
|
export interface SessionUsage {
|
|
18
19
|
inputTokens: number;
|
|
19
20
|
outputTokens: number;
|
|
21
|
+
/** Sum of the full-call totals the harness itself reported, when it reported one. */
|
|
22
|
+
reportedTokens: number;
|
|
23
|
+
/** Steps whose usage carried a harness total. */
|
|
24
|
+
reportedSteps: number;
|
|
25
|
+
/** Steps whose usage carried none, so the billed parts had to stand in. */
|
|
26
|
+
unreportedSteps: number;
|
|
20
27
|
cacheReadTokens: number;
|
|
21
28
|
cacheWriteTokens: number;
|
|
22
29
|
}
|
|
30
|
+
/** The session-size number the second row shows, and how it was derived. */
|
|
31
|
+
export interface SessionTokenTotal {
|
|
32
|
+
tokens: number;
|
|
33
|
+
/**
|
|
34
|
+
* `harness` when every usage sample carried the harness's own total; `sum`
|
|
35
|
+
* when some step's billed parts had to be added in instead.
|
|
36
|
+
*/
|
|
37
|
+
basis: 'harness' | 'sum';
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* How many tokens this session has moved, on the accounting the harness
|
|
41
|
+
* published.
|
|
42
|
+
*
|
|
43
|
+
* The harness is the authority: a `totalTokens` it reports is a full-call total
|
|
44
|
+
* whose parts are defined provider by provider — pi-ai's own adapters build it
|
|
45
|
+
* as prompt + output + cache read + cache write — and re-deriving one from the
|
|
46
|
+
* three prompt counters plus output is exactly the arithmetic that
|
|
47
|
+
* double-counts on a provider that folds cache into its prompt count. So
|
|
48
|
+
* reported totals are summed, and only a step that reported none contributes
|
|
49
|
+
* its billed parts. The basis says which happened, and `/status` prints it.
|
|
50
|
+
*/
|
|
51
|
+
export declare function sessionTokenTotal(usage: SessionUsage): SessionTokenTotal | undefined;
|
|
23
52
|
export interface SessionStatsSnapshot {
|
|
24
53
|
turns: number;
|
|
25
54
|
steps: number;
|
|
@@ -29,6 +58,12 @@ export interface SessionStatsSnapshot {
|
|
|
29
58
|
ttftSteps: number;
|
|
30
59
|
decodeMs: number;
|
|
31
60
|
decodeTokens: number;
|
|
61
|
+
/** Tokens and milliseconds of the most recent settled step alone. */
|
|
62
|
+
lastDecodeMs: number;
|
|
63
|
+
lastDecodeTokens: number;
|
|
64
|
+
/** Decode totals excluding the step that is currently open. */
|
|
65
|
+
settledDecodeMs: number;
|
|
66
|
+
settledDecodeTokens: number;
|
|
32
67
|
usage: SessionUsage;
|
|
33
68
|
}
|
|
34
69
|
/** Flat view of the same numbers, for the footer's stats line. */
|
|
@@ -45,6 +80,7 @@ export interface SessionStatsRow {
|
|
|
45
80
|
outputTokens: number;
|
|
46
81
|
cacheReadTokens: number;
|
|
47
82
|
cacheWriteTokens: number;
|
|
83
|
+
totalTokens: number;
|
|
48
84
|
}
|
|
49
85
|
export interface OpenStep {
|
|
50
86
|
turn: number;
|
|
@@ -52,7 +88,7 @@ export interface OpenStep {
|
|
|
52
88
|
}
|
|
53
89
|
export declare function emptySessionUsage(): SessionUsage;
|
|
54
90
|
export declare function emptySessionStats(): SessionStatsSnapshot;
|
|
55
|
-
/** Flatten a snapshot into the
|
|
91
|
+
/** Flatten a snapshot into the legacy stats-row shape. */
|
|
56
92
|
export declare function statsRowOf(stats: SessionStatsSnapshot): SessionStatsRow;
|
|
57
93
|
/**
|
|
58
94
|
* Accumulates the stats the footer and `/status` show as session events arrive.
|
|
@@ -65,10 +101,35 @@ export declare class SessionStatsTracker {
|
|
|
65
101
|
private readonly usageByStep;
|
|
66
102
|
private readonly pendingToolTimes;
|
|
67
103
|
private lastTurn;
|
|
104
|
+
/** Live decode estimate for the step currently streaming. */
|
|
105
|
+
private readonly live;
|
|
68
106
|
/** The step a live chunk belongs to when the host frame carried none. */
|
|
69
107
|
currentStep(): OpenStep | undefined;
|
|
108
|
+
/**
|
|
109
|
+
* The open step's two clocks, for the status row's elapsed suffix.
|
|
110
|
+
*
|
|
111
|
+
* Both are host wall clocks, so a caller may subtract them from `Date.now()`
|
|
112
|
+
* without a translation. `firstTokenAt` is absent until the step's first
|
|
113
|
+
* delta, which is exactly the difference between "the request is out" and
|
|
114
|
+
* "the model is talking".
|
|
115
|
+
*/
|
|
116
|
+
stepClocks(): {
|
|
117
|
+
startedAt?: number;
|
|
118
|
+
firstTokenAt?: number;
|
|
119
|
+
};
|
|
70
120
|
/** `step/start`: opens the clock LLM time, TTFT and decode are measured from. */
|
|
71
121
|
noteStepStart(turn: number, step: number, time: number): void;
|
|
122
|
+
/**
|
|
123
|
+
* A live fragment of model output, for the running throughput estimate.
|
|
124
|
+
*
|
|
125
|
+
* The text itself travels: the estimate is tokens per character, and how many
|
|
126
|
+
* tokens a character is worth depends on the script the fragment is written in.
|
|
127
|
+
* @param text - the fragment.
|
|
128
|
+
* @param time - wall clock the fragment arrived at.
|
|
129
|
+
*/
|
|
130
|
+
noteDelta(text: string, time?: number): void;
|
|
131
|
+
/** The running throughput estimate, settled value included. */
|
|
132
|
+
throughput(now?: number): ThroughputView;
|
|
72
133
|
/** The first token delta of the open step latches its TTFT clock. */
|
|
73
134
|
noteFirstToken(turn: number, step: number, time: number): void;
|
|
74
135
|
/** Replace one step's usage sample so a repeated report never double counts. */
|
|
@@ -87,6 +148,8 @@ export declare class SessionStatsTracker {
|
|
|
87
148
|
}): void;
|
|
88
149
|
/** `tool/call`: start the tool clock for this call. */
|
|
89
150
|
noteToolStart(callId: string, time: number): void;
|
|
151
|
+
/** When a still-open tool call started, for the status row's elapsed suffix. */
|
|
152
|
+
toolStartedAt(callId: string): number | undefined;
|
|
90
153
|
/** `tool/result`: add the elapsed tool time, once. */
|
|
91
154
|
noteToolEnd(callId: string, time: number): void;
|
|
92
155
|
/** `step/end`: count the step, and forget the step's usage dedupe key. */
|
package/lib/types/term-text.d.ts
CHANGED
|
@@ -40,6 +40,16 @@ export declare function waitCardCopy(input: {
|
|
|
40
40
|
export declare function wrapWaitDetails(detail: string, width: number, maxLines?: number): string[];
|
|
41
41
|
/** Forget the cached decision, so the next read re-consults the environment. */
|
|
42
42
|
export declare function resetAsciiChrome(): void;
|
|
43
|
+
/**
|
|
44
|
+
* Whether chrome is being drawn in ASCII, for renderers that pick the glyph run
|
|
45
|
+
* themselves.
|
|
46
|
+
*
|
|
47
|
+
* `pinEmojiCells` translates them anyway; asking is for the case where the
|
|
48
|
+
* *shape* differs rather than just the character — a metre whose filled cell is
|
|
49
|
+
* `#` reads as `##..`, not as `#..#`, so the renderer wants to know before it
|
|
50
|
+
* counts and pads.
|
|
51
|
+
*/
|
|
52
|
+
export declare function asciiChromeEnabled(): boolean;
|
|
43
53
|
/**
|
|
44
54
|
* Rewrite one string's chrome into ASCII when the terminal cannot decode UTF-8.
|
|
45
55
|
*
|
|
@@ -112,6 +122,28 @@ export declare function ambiguousWidthMeasured(): boolean | undefined;
|
|
|
112
122
|
* @returns true when those glyphs should be budgeted two cells.
|
|
113
123
|
*/
|
|
114
124
|
export declare function ambiguousWidthIsTwo(env?: NodeJS.ProcessEnv, onTerminal?: boolean): boolean;
|
|
125
|
+
/**
|
|
126
|
+
* Cells one *code unit* costs at its position, given a resolved policy.
|
|
127
|
+
*
|
|
128
|
+
* The per-character core of {@link displayWidth}, split out for the
|
|
129
|
+
* callers that walk a string themselves (clipping, truncation, wrapping): they
|
|
130
|
+
* resolve {@link ambiguousPolicy} once per string and then ask this per
|
|
131
|
+
* character, instead of paying `displayWidth`'s policy lookup and ASCII mapping
|
|
132
|
+
* for every character of every row. A profile of a window drag put 56% of the
|
|
133
|
+
* samples in that lookup, because the clipping loops are O(n²) in a way nothing
|
|
134
|
+
* about their shape advertises: `clipAnsiToWidth` calls this for each character,
|
|
135
|
+
* and `truncateToWidth` re-measures the whole remainder inside its loop.
|
|
136
|
+
*
|
|
137
|
+
* ASCII is answered from its code point before any table lookup: it is the
|
|
138
|
+
* overwhelming majority of every row, and it can never be ambiguous or wide.
|
|
139
|
+
* @param cp - the code point.
|
|
140
|
+
* @param policy - the resolved policy, as returned by {@link ambiguousPolicy}.
|
|
141
|
+
* @returns 0, 1 or 2 cells.
|
|
142
|
+
*/
|
|
143
|
+
export declare function charCellWidth(cp: number, policy: {
|
|
144
|
+
wide: boolean;
|
|
145
|
+
reserve: boolean;
|
|
146
|
+
}): number;
|
|
115
147
|
export declare function displayWidth(text: string): number;
|
|
116
148
|
/**
|
|
117
149
|
* Make a painted row spend the two cells {@link displayWidth} budgets for every
|
|
@@ -216,7 +248,19 @@ export declare function hrefAtColumn(hits: readonly PaintedLinkHit[], col: numbe
|
|
|
216
248
|
* headings, quotes, lists, rules, links and inline spans keep their own ANSI.
|
|
217
249
|
*/
|
|
218
250
|
export declare function renderMarkdownLines(text: string, width: number, color: boolean, hyperlinks?: boolean): string[];
|
|
219
|
-
/**
|
|
251
|
+
/**
|
|
252
|
+
* Cut **one line** to fit a width, appending an ellipsis when truncated.
|
|
253
|
+
*
|
|
254
|
+
* A line break is not a character in a row: it moves the terminal to the next row,
|
|
255
|
+
* and a frame that addresses its rows absolutely (`ESC[row;1H`) then writes every
|
|
256
|
+
* following row one line off — content lands on the composer, on the footer, and on
|
|
257
|
+
* top of whatever was already there. `sanitizeTerminalText` keeps LF/CR on purpose
|
|
258
|
+
* (it also sanitizes multi-line bodies), so the collapse happens here, where the
|
|
259
|
+
* contract is "one row": each break becomes a single space, which keeps the string
|
|
260
|
+
* the same *length* — callers that pass this result into `paintSegmentedLine` rely
|
|
261
|
+
* on character offsets into the text they built, and a collapsing replacement would
|
|
262
|
+
* silently move every colour segment.
|
|
263
|
+
*/
|
|
220
264
|
export declare function truncateToWidth(text: string, width: number): string;
|
|
221
265
|
/**
|
|
222
266
|
* Clip an already-styled ANSI line to `width` terminal cells without dropping
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live decode throughput for the status row.
|
|
3
|
+
*
|
|
4
|
+
* The harness never publishes tokens per second. What it does publish, per
|
|
5
|
+
* step, is authoritative: `assistant/message` carries the step's output-token
|
|
6
|
+
* count and the packed stream's first-token time, so `decodeTokens / decodeMs`
|
|
7
|
+
* over settled steps is a real measurement. What it does *not* publish is any
|
|
8
|
+
* per-token count while a step is still streaming — adapters emit text deltas
|
|
9
|
+
* and only hand over `usage` as the stream closes (verified against
|
|
10
|
+
* `dsh-llm-deepseek` and `dsh-llm-pi-ai`).
|
|
11
|
+
*
|
|
12
|
+
* So there are exactly two honest numbers, and this module keeps them apart:
|
|
13
|
+
*
|
|
14
|
+
* 1. **Settled** — tokens and milliseconds of a completed step. Exact, and the
|
|
15
|
+
* only number that is ever comparable across steps or sessions.
|
|
16
|
+
* 2. **Live** — characters per second while a step streams, converted with a
|
|
17
|
+
* ratio calibrated from this session's own settled steps. It is an estimate,
|
|
18
|
+
* it is labelled with `~`, and it exists so a long reply shows *something*
|
|
19
|
+
* that moves instead of a number frozen at the previous step.
|
|
20
|
+
*
|
|
21
|
+
* There is deliberately no health grading here. Tokens per second is a property
|
|
22
|
+
* of the model, the provider, the reasoning effort and the prompt, not a signal
|
|
23
|
+
* that something is wrong: 20 tok/s from a reasoning model and 20 tok/s from a
|
|
24
|
+
* small fast one mean different things. The row paints it neutral.
|
|
25
|
+
*
|
|
26
|
+
* @module dsh-ssh-tui/throughput
|
|
27
|
+
*/
|
|
28
|
+
/** How much of the recent past the live rate is measured over. */
|
|
29
|
+
export declare const THROUGHPUT_WINDOW_MS = 4000;
|
|
30
|
+
/** Minimum span between the two samples a rate is taken from. */
|
|
31
|
+
export declare const THROUGHPUT_MIN_SPAN_MS = 400;
|
|
32
|
+
/** A live rate older than this describes a stream that stopped, not one running. */
|
|
33
|
+
export declare const THROUGHPUT_FRESH_MS = 2500;
|
|
34
|
+
/**
|
|
35
|
+
* Tokens per character, for the two ends of the script spectrum.
|
|
36
|
+
*
|
|
37
|
+
* Latin text averages roughly a third of a token per character; CJK is close to
|
|
38
|
+
* one, because a Chinese character is usually its own token and most words are one
|
|
39
|
+
* or two characters. Between those ends the estimate is interpolated by the
|
|
40
|
+
* *measured* script mix of the stream being estimated, continuously: a threshold
|
|
41
|
+
* ("30% CJK means Chinese") would put a mostly-English reply with one Chinese
|
|
42
|
+
* paragraph on the wrong side of a cliff, and the number it produces is shown to
|
|
43
|
+
* the reader.
|
|
44
|
+
*/
|
|
45
|
+
export declare const DEFAULT_TOKENS_PER_CHAR_LATIN = 0.32;
|
|
46
|
+
export declare const DEFAULT_TOKENS_PER_CHAR_CJK = 0.85;
|
|
47
|
+
/** The Latin end under its original name: the fallback for a stream with no text. */
|
|
48
|
+
export declare const DEFAULT_TOKENS_PER_CHAR = 0.32;
|
|
49
|
+
/** What the status row paints, and what `/status` reports. */
|
|
50
|
+
export interface ThroughputView {
|
|
51
|
+
/** Exact tokens per second of the most recent settled step, when it had usage. */
|
|
52
|
+
settledRate?: number;
|
|
53
|
+
/** Characters the live estimate was built from, for the calibration report. */
|
|
54
|
+
liveChars: number;
|
|
55
|
+
/** Estimated tokens per second over the streaming window. */
|
|
56
|
+
liveRate?: number;
|
|
57
|
+
/** True when `liveRate` was measured recently enough to call it current. */
|
|
58
|
+
fresh: boolean;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Characters to tokens, bounded and finite.
|
|
62
|
+
*
|
|
63
|
+
* The default is an average over mixed prose and code; every settled step
|
|
64
|
+
* replaces half of it with this session's own measurement, which is what makes
|
|
65
|
+
* the live estimate usable on CJK output (where one character is close to one
|
|
66
|
+
* token) without hardcoding a language.
|
|
67
|
+
*/
|
|
68
|
+
export declare function boundTokensPerChar(value: number, fallback?: number): number;
|
|
69
|
+
/**
|
|
70
|
+
* The median of up to the last three rates, or the newest one alone.
|
|
71
|
+
*
|
|
72
|
+
* A single frame in which the terminal coalesced a burst of tokens into one
|
|
73
|
+
* delta reads as a spike; a median of three ignores it, while a real change
|
|
74
|
+
* survives because two agreeing samples decide it. Same rule, and the same
|
|
75
|
+
* reasoning, as the link chip's RTT median.
|
|
76
|
+
*/
|
|
77
|
+
export declare function medianRate(values: readonly number[]): number | undefined;
|
|
78
|
+
/**
|
|
79
|
+
* Tracks one step's live decode rate from character deltas.
|
|
80
|
+
*
|
|
81
|
+
* Sampling is the caller's job (`note` per fragment): the window's own
|
|
82
|
+
* minimum span is what keeps the number stable, so a burst of tiny deltas and
|
|
83
|
+
* one coalesced delta produce the same answer.
|
|
84
|
+
*/
|
|
85
|
+
/**
|
|
86
|
+
* Whether one code point is written in a script whose characters are close to one
|
|
87
|
+
* token each: CJK ideographs, kana, Hangul syllables, and the fullwidth or CJK
|
|
88
|
+
* punctuation that travels with them.
|
|
89
|
+
*
|
|
90
|
+
* Deliberately a property of the *text* rather than of the reader's locale: a
|
|
91
|
+
* Chinese reader's English reply is Latin text and costs Latin tokens per
|
|
92
|
+
* character, and `~tok/s` has to describe the tokens, not the person.
|
|
93
|
+
*/
|
|
94
|
+
export declare function isCjkCodePoint(codePoint: number): boolean;
|
|
95
|
+
/**
|
|
96
|
+
* The starting ratio for a stream of a given script mix: `cjkShare` of the
|
|
97
|
+
* characters read as CJK, the rest as Latin. Continuous by construction, so a
|
|
98
|
+
* reply that is 70% English and 30% Chinese lands 30% of the way up the range
|
|
99
|
+
* instead of being classified as one or the other.
|
|
100
|
+
*/
|
|
101
|
+
export declare function scriptTokensPerChar(cjkShare: number): number;
|
|
102
|
+
export declare class ThroughputTracker {
|
|
103
|
+
/** The session's own ratio, once a step has settled. */
|
|
104
|
+
private calibrated;
|
|
105
|
+
/** Characters and CJK characters seen in the step being estimated. */
|
|
106
|
+
private observedChars;
|
|
107
|
+
private observedCjk;
|
|
108
|
+
private samples;
|
|
109
|
+
private chars;
|
|
110
|
+
private baseAt;
|
|
111
|
+
private startedAt;
|
|
112
|
+
private lastDeltaAt;
|
|
113
|
+
private lastStepChars;
|
|
114
|
+
/** Rates already computed this step, kept for the median. */
|
|
115
|
+
private recentRates;
|
|
116
|
+
private lastRate;
|
|
117
|
+
private settledRate;
|
|
118
|
+
/**
|
|
119
|
+
* The ratio the estimate is using right now: this session's own measurement once
|
|
120
|
+
* a step has settled, and the script mix of the stream being estimated before
|
|
121
|
+
* that. `/status` and the tests read it.
|
|
122
|
+
*/
|
|
123
|
+
tokensPerChar(): number;
|
|
124
|
+
/** Share of this step's characters that were CJK, 0 when none have arrived. */
|
|
125
|
+
cjkShare(): number;
|
|
126
|
+
/** Whether the ratio is a settled measurement rather than a script estimate. */
|
|
127
|
+
calibratedRatio(): boolean;
|
|
128
|
+
/** Drop a finished step's stream state, keeping calibration and last settle. */
|
|
129
|
+
reset(): void;
|
|
130
|
+
/**
|
|
131
|
+
* One observed fragment of model output.
|
|
132
|
+
*
|
|
133
|
+
* The fragment itself, not a count: the estimate is tokens per character, and
|
|
134
|
+
* how many tokens a character is worth depends on which characters they are, so
|
|
135
|
+
* the script mix has to be measured here rather than assumed once for the
|
|
136
|
+
* session. Code points rather than UTF-16 units, so an emoji or a rare ideograph
|
|
137
|
+
* outside the BMP counts as the one character the reader sees.
|
|
138
|
+
* @param text - the fragment; empty is ignored.
|
|
139
|
+
* @param time - wall clock the fragment arrived at, for tests and replay.
|
|
140
|
+
*/
|
|
141
|
+
note(text: string, time?: number): void;
|
|
142
|
+
/**
|
|
143
|
+
* Settle a step: fold its exact decode rate into the calibration and into the
|
|
144
|
+
* number an idle row shows.
|
|
145
|
+
* @param outputTokens - the step's authoritative output tokens.
|
|
146
|
+
* @param decodeMs - milliseconds from first token to the settled message.
|
|
147
|
+
*/
|
|
148
|
+
settle(outputTokens: number, decodeMs: number): void;
|
|
149
|
+
/** What the status row paints, and what `/status` reports. */
|
|
150
|
+
view(now?: number): ThroughputView;
|
|
151
|
+
/** Drop samples that fell out of the window, and keep the newest few. */
|
|
152
|
+
private trim;
|
|
153
|
+
/** Recompute the window's rate and fold it into the median buffer. */
|
|
154
|
+
private sampleRate;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* A rate as the row paints it: whole numbers, `<1` rather than nothing.
|
|
158
|
+
*
|
|
159
|
+
* A slow step that decoded 3 tokens over 30 seconds is still decoding; rounding
|
|
160
|
+
* it to `0` reads as a stalled model. Above 1000 the number is compacted, since
|
|
161
|
+
* four digits is cells the quota and context meters want more.
|
|
162
|
+
*/
|
|
163
|
+
export declare function formatRate(tokensPerSecond: number): string;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import type { AskUserQuestionItem } from '@deepseek-ai/dsh-user-questions';
|
|
5
5
|
import { type TextSegment } from './term-text.js';
|
|
6
6
|
import type { DiffDisplayLine, Row, ToolDiffHunk } from './transcript-types.js';
|
|
7
|
+
import { type ToolApproval } from './approval-state.js';
|
|
7
8
|
export declare const SHELL_TOOL_NAMES: Set<string>;
|
|
8
9
|
export declare const DIFF_TOOL_NAMES: Set<string>;
|
|
9
10
|
export declare const JOB_TOOL_NAMES: Set<string>;
|
|
@@ -104,6 +105,26 @@ export declare const SUBAGENT_TOOL_NAMES: Set<string>;
|
|
|
104
105
|
* just duplicates chrome.
|
|
105
106
|
*/
|
|
106
107
|
export declare const HIDDEN_TOOL_NAMES: Set<string>;
|
|
108
|
+
/**
|
|
109
|
+
* Tools whose card is the *question* they asked, not a generic tool card (B2.4).
|
|
110
|
+
*
|
|
111
|
+
* `ask_user_question` is the only one: its durable representation is the question
|
|
112
|
+
* card built from the call's arguments, and drawing both was the duplicate this
|
|
113
|
+
* round removes. It is a separate set from {@link HIDDEN_TOOL_NAMES} on purpose —
|
|
114
|
+
* hidden means "no representation at all" (`get_goal`), while this means "the
|
|
115
|
+
* representation is drawn by the question projection". A tool card that never
|
|
116
|
+
* appears is a *different* decision from one whose content is shown elsewhere, and
|
|
117
|
+
* the audit has to be able to tell them apart.
|
|
118
|
+
*/
|
|
119
|
+
export declare const QUESTION_TOOL_NAMES: Set<string>;
|
|
120
|
+
/**
|
|
121
|
+
* Tools whose card is the *artifact* they act on, not a tool card of their own.
|
|
122
|
+
*
|
|
123
|
+
* `exit_plan_mode` is the plan review: the plan artifact carries the reviewed body,
|
|
124
|
+
* the review's outcome and the lifecycle lines, so a generic card beside all of that
|
|
125
|
+
* was a third representation of one event (B2.5 §15).
|
|
126
|
+
*/
|
|
127
|
+
export declare const PLAN_TOOL_NAMES: Set<string>;
|
|
107
128
|
export declare function toolTitle(name: string): string;
|
|
108
129
|
export declare function planReviewOf(question: AskUserQuestionItem): boolean;
|
|
109
130
|
/** Derive the intended file change from a mutation tool's arguments. */
|
|
@@ -142,10 +163,30 @@ export declare function buildToolHeader(input: {
|
|
|
142
163
|
add: number;
|
|
143
164
|
del: number;
|
|
144
165
|
};
|
|
166
|
+
/**
|
|
167
|
+
* The approval that guarded this call (B2.4), when one did.
|
|
168
|
+
*
|
|
169
|
+
* It rides on the title — right after the tool's own name — rather than at the
|
|
170
|
+
* end of the row: the tail is what a narrow terminal truncates first, and a card
|
|
171
|
+
* whose whole point is "this needed a decision" must not be the one card that
|
|
172
|
+
* loses it. It is also the only place the state is shown, since approval no
|
|
173
|
+
* longer writes a row of its own.
|
|
174
|
+
*/
|
|
175
|
+
approval?: ToolApproval;
|
|
145
176
|
}): {
|
|
146
177
|
plain: string;
|
|
147
178
|
segments: TextSegment[];
|
|
148
179
|
};
|
|
180
|
+
/**
|
|
181
|
+
* The title chip: the state, and — when the reader is being told something
|
|
182
|
+
* weaker — why they can trust it less.
|
|
183
|
+
*
|
|
184
|
+
* `live` and `durable` are the same fact at two moments (a person decided, and
|
|
185
|
+
* the log still says so), so they print the state alone. `policy` adds `auto`,
|
|
186
|
+
* and `inferred` says so: a resume that found only the Harness's refusal sentence
|
|
187
|
+
* in a tool result is a reading, and the card has to admit it.
|
|
188
|
+
*/
|
|
189
|
+
export declare function approvalChipText(approval: ToolApproval): string;
|
|
149
190
|
/** How many terminal rows a tool body occupies after wrapping. */
|
|
150
191
|
export declare function wrappedToolBodyLineCount(lines: readonly {
|
|
151
192
|
text: string;
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
* Kept free of the SshTui class so leaf modules can import them.
|
|
4
4
|
*/
|
|
5
5
|
export type DisconnectPolicyName = 'pause' | 'continue';
|
|
6
|
+
import type { AskUserQuestionOption } from '@deepseek-ai/dsh-user-questions';
|
|
7
|
+
import type { ToolApproval } from './approval-state.js';
|
|
8
|
+
import type { PlanProvenance, PlanState } from './plan-projection.js';
|
|
9
|
+
import type { RepresentationMeta } from './representation.js';
|
|
6
10
|
export type SubagentLogKind = 'user' | 'assistant' | 'tool' | 'result' | 'turn' | 'approval' | 'team' | 'system';
|
|
7
11
|
/** One child-session event folded into a parent-side subagent card. */
|
|
8
12
|
export interface SubagentLogEntry {
|
|
@@ -23,7 +27,19 @@ export interface PlanTodoItem {
|
|
|
23
27
|
*/
|
|
24
28
|
status: 'pending' | 'in_progress' | 'completed' | 'failed' | 'skipped';
|
|
25
29
|
}
|
|
26
|
-
|
|
30
|
+
/**
|
|
31
|
+
* A transcript line, plus the policy that produced it.
|
|
32
|
+
*
|
|
33
|
+
* The metadata is attached by `pushRow` (the only entry), which is why it is
|
|
34
|
+
* optional in the *shape*: a creation site writes a plain row and names its source,
|
|
35
|
+
* and the audit reads the classification back off the row afterwards. It is
|
|
36
|
+
* deliberately not a field each site fills in by hand — one owner, one place to get
|
|
37
|
+
* it wrong.
|
|
38
|
+
*/
|
|
39
|
+
export type Row = RowShape & {
|
|
40
|
+
representation?: RepresentationMeta;
|
|
41
|
+
};
|
|
42
|
+
type RowShape = {
|
|
27
43
|
kind: 'user';
|
|
28
44
|
text: string;
|
|
29
45
|
} | {
|
|
@@ -63,6 +79,14 @@ export type Row = {
|
|
|
63
79
|
totalLines?: number;
|
|
64
80
|
/** Flip-card animation until this timestamp (ms since epoch). */
|
|
65
81
|
flipUntil?: number;
|
|
82
|
+
/**
|
|
83
|
+
* The approval that guarded this call, when one did (B2.4).
|
|
84
|
+
*
|
|
85
|
+
* A field of the tool card, not a row of its own: approval is a state of
|
|
86
|
+
* the call. `provenance` says how well it is known, so a resume can never
|
|
87
|
+
* dress an inference up as a decision — see `approval-state.ts`.
|
|
88
|
+
*/
|
|
89
|
+
approval?: ToolApproval;
|
|
66
90
|
} | {
|
|
67
91
|
kind: 'subagent';
|
|
68
92
|
sessionId: string;
|
|
@@ -96,6 +120,23 @@ export type Row = {
|
|
|
96
120
|
expanded: boolean;
|
|
97
121
|
/** When true the plan stays in the scrolling transcript, not the dock. */
|
|
98
122
|
archived?: boolean;
|
|
123
|
+
/**
|
|
124
|
+
* The plan artifact this row stands for (B2.5).
|
|
125
|
+
*
|
|
126
|
+
* The row is a *reference*: its state, its steps and its lifecycle come from the
|
|
127
|
+
* projection of durable events, and the id is derived from the sequence that
|
|
128
|
+
* opened the artifact — so a replay finds the same row again instead of
|
|
129
|
+
* rebuilding "the newest live plan" from display order.
|
|
130
|
+
*/
|
|
131
|
+
artifactId?: string;
|
|
132
|
+
/** The artifact's folded state. */
|
|
133
|
+
state?: PlanState;
|
|
134
|
+
/** How that state is known: an event said it, or it was read off the log. */
|
|
135
|
+
provenance?: PlanProvenance;
|
|
136
|
+
/** The reader's words when they refused a review with feedback. */
|
|
137
|
+
reviewFeedback?: string;
|
|
138
|
+
/** The revision the two display flags below were last computed for. */
|
|
139
|
+
lastRevisionId?: string;
|
|
99
140
|
/**
|
|
100
141
|
* Display-only: the last turn ended while todos were still open.
|
|
101
142
|
* Does not rewrite the session log.
|
|
@@ -112,11 +153,30 @@ export type Row = {
|
|
|
112
153
|
} | {
|
|
113
154
|
kind: 'question';
|
|
114
155
|
questionId: string;
|
|
156
|
+
/**
|
|
157
|
+
* The `ask_user_question` call this question came from, when the Session
|
|
158
|
+
* recorded one. It is what ties the card to the durable projection, so a
|
|
159
|
+
* timed question answered *after* its window closed can be settled from the
|
|
160
|
+
* log instead of from a request that no longer exists.
|
|
161
|
+
*/
|
|
162
|
+
callId?: string;
|
|
115
163
|
title: string;
|
|
116
164
|
header?: string;
|
|
117
165
|
detail?: string;
|
|
118
166
|
intent: 'ask' | 'plan-review';
|
|
167
|
+
/**
|
|
168
|
+
* The choices the call offered, kept so a question the Session still holds
|
|
169
|
+
* can be answered *as the question it was*: without them a second answer is
|
|
170
|
+
* a free-text box, and `ctx.userQuestions.answer` is handed a batch that does
|
|
171
|
+
* not match the call's own shape.
|
|
172
|
+
*/
|
|
173
|
+
options?: AskUserQuestionOption[];
|
|
174
|
+
multiSelect?: boolean;
|
|
119
175
|
status: 'waiting' | 'answered' | 'cancelled';
|
|
176
|
+
/** True when the Session still accepts an answer for this call. */
|
|
177
|
+
continued?: boolean;
|
|
178
|
+
/** True once the durable projection, not a live request, owns this card. */
|
|
179
|
+
durable?: boolean;
|
|
120
180
|
summary: string;
|
|
121
181
|
expanded: boolean;
|
|
122
182
|
} | {
|
|
@@ -197,7 +257,7 @@ export type CollapsibleBlock = Extract<Row, {
|
|
|
197
257
|
kind: 'streaming-reasoning';
|
|
198
258
|
expanded: boolean;
|
|
199
259
|
};
|
|
200
|
-
export type DisplayKind = Row['kind'] | 'tool-result' | 'diff-add' | 'diff-del' | 'diff-path' | 'todo-done' | 'todo-active' | 'todo-pending' | 'todo-failed' | 'todo-skipped' | 'plan-dock' | 'subagent-header';
|
|
260
|
+
export type DisplayKind = Row['kind'] | 'tool-result' | 'diff-add' | 'diff-del' | 'diff-path' | 'todo-done' | 'todo-active' | 'todo-pending' | 'todo-failed' | 'todo-skipped' | 'plan-dock' | 'subagent-header' | 'setup-step' | 'setup-choice' | 'setup-field' | 'setup-step' | 'setup-choice' | 'setup-field';
|
|
201
261
|
/** One rendered diff/inspect body line with its display role. */
|
|
202
262
|
export interface DiffDisplayLine {
|
|
203
263
|
kind: DisplayKind;
|
|
@@ -233,3 +293,4 @@ export interface ToolDiffHunk {
|
|
|
233
293
|
oldText: string | null;
|
|
234
294
|
newText: string;
|
|
235
295
|
}
|
|
296
|
+
export {};
|