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/theme.js
CHANGED
|
@@ -11,7 +11,12 @@ const DEFAULT_THEME = {
|
|
|
11
11
|
tokens: {
|
|
12
12
|
user: '36',
|
|
13
13
|
assistant: '',
|
|
14
|
-
|
|
14
|
+
// `2` (faint) is not implemented everywhere — a 256-colour terminal that
|
|
15
|
+
// ignores it draws the reasoning rows in the *default* foreground, i.e. as
|
|
16
|
+
// bright as the reply beside them, which is how the card was reported as
|
|
17
|
+
// white. The colour is what carries "muted"; the attributes only sharpen it
|
|
18
|
+
// where the terminal honours them.
|
|
19
|
+
reasoning: '2;3;90',
|
|
15
20
|
brand: '1;38;2;77;107;253',
|
|
16
21
|
tool: '',
|
|
17
22
|
'tool-result': '',
|
|
@@ -24,6 +29,9 @@ const DEFAULT_THEME = {
|
|
|
24
29
|
'todo-skipped': '2;33',
|
|
25
30
|
'todo-pending': '90',
|
|
26
31
|
'plan-dock': '38;5;180',
|
|
32
|
+
'setup-step': '1;36',
|
|
33
|
+
'setup-choice': '33',
|
|
34
|
+
'setup-field': '1;37',
|
|
27
35
|
'subagent-header': '38;5;141',
|
|
28
36
|
diag: '2;36',
|
|
29
37
|
error: '31',
|
|
@@ -61,7 +69,7 @@ const CATPPUCCIN_THEME = {
|
|
|
61
69
|
tokens: {
|
|
62
70
|
user: '38;2;137;180;250',
|
|
63
71
|
assistant: '',
|
|
64
|
-
reasoning: '2;3',
|
|
72
|
+
reasoning: '2;3;38;2;166;173;200',
|
|
65
73
|
brand: '1;38;2;203;166;247',
|
|
66
74
|
tool: '',
|
|
67
75
|
'tool-result': '',
|
|
@@ -74,6 +82,9 @@ const CATPPUCCIN_THEME = {
|
|
|
74
82
|
'todo-skipped': '2;38;2;249;226;175',
|
|
75
83
|
'todo-pending': '38;2;108;112;134',
|
|
76
84
|
'plan-dock': '38;2;250;179;135',
|
|
85
|
+
'setup-step': '1;36',
|
|
86
|
+
'setup-choice': '33',
|
|
87
|
+
'setup-field': '1;37',
|
|
77
88
|
'subagent-header': '38;2;203;166;247',
|
|
78
89
|
diag: '2;38;2;148;226;213',
|
|
79
90
|
error: '38;2;243;139;168',
|
|
@@ -111,7 +122,7 @@ const GRUVBOX_THEME = {
|
|
|
111
122
|
tokens: {
|
|
112
123
|
user: '38;2;131;165;152',
|
|
113
124
|
assistant: '',
|
|
114
|
-
reasoning: '2;3',
|
|
125
|
+
reasoning: '2;3;38;2;168;153;132',
|
|
115
126
|
brand: '1;38;2;211;134;155',
|
|
116
127
|
tool: '',
|
|
117
128
|
'tool-result': '',
|
|
@@ -124,6 +135,9 @@ const GRUVBOX_THEME = {
|
|
|
124
135
|
'todo-skipped': '2;38;2;250;189;47',
|
|
125
136
|
'todo-pending': '38;2;146;131;116',
|
|
126
137
|
'plan-dock': '38;2;254;128;25',
|
|
138
|
+
'setup-step': '1;36',
|
|
139
|
+
'setup-choice': '33',
|
|
140
|
+
'setup-field': '1;37',
|
|
127
141
|
'subagent-header': '38;2;211;134;155',
|
|
128
142
|
diag: '2;38;2;142;192;124',
|
|
129
143
|
error: '38;2;251;73;52',
|
|
@@ -182,6 +196,9 @@ const MONO_THEME = {
|
|
|
182
196
|
'todo-skipped': '2;3',
|
|
183
197
|
'todo-pending': '2',
|
|
184
198
|
'plan-dock': '1',
|
|
199
|
+
'setup-step': '1',
|
|
200
|
+
'setup-choice': '4',
|
|
201
|
+
'setup-field': '',
|
|
185
202
|
'subagent-header': '4',
|
|
186
203
|
diag: '2;3',
|
|
187
204
|
error: '1;4',
|
package/lib/theme.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme.js","sourceRoot":"","sources":["../src/theme.ts"],"names":[],"mappings":"AAmEA;;;;;;GAMG;AACH,MAAM,aAAa,GAAU;IAC3B,IAAI,EAAE,SAAS;IACf,IAAI,EAAE,CAAC;IACP,MAAM,EAAE;QACN,IAAI,EAAE,IAAI;QACV,SAAS,EAAE,EAAE;QACb,SAAS,EAAE,
|
|
1
|
+
{"version":3,"file":"theme.js","sourceRoot":"","sources":["../src/theme.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,305 @@
|
|
|
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 const THROUGHPUT_WINDOW_MS = 4_000;
|
|
30
|
+
/** Minimum span between the two samples a rate is taken from. */
|
|
31
|
+
export const THROUGHPUT_MIN_SPAN_MS = 400;
|
|
32
|
+
/** A live rate older than this describes a stream that stopped, not one running. */
|
|
33
|
+
export const THROUGHPUT_FRESH_MS = 2_500;
|
|
34
|
+
/** Samples kept in the window; the cadence is the paint interval, not per token. */
|
|
35
|
+
const MAX_SAMPLES = 64;
|
|
36
|
+
/**
|
|
37
|
+
* Tokens per character, for the two ends of the script spectrum.
|
|
38
|
+
*
|
|
39
|
+
* Latin text averages roughly a third of a token per character; CJK is close to
|
|
40
|
+
* one, because a Chinese character is usually its own token and most words are one
|
|
41
|
+
* or two characters. Between those ends the estimate is interpolated by the
|
|
42
|
+
* *measured* script mix of the stream being estimated, continuously: a threshold
|
|
43
|
+
* ("30% CJK means Chinese") would put a mostly-English reply with one Chinese
|
|
44
|
+
* paragraph on the wrong side of a cliff, and the number it produces is shown to
|
|
45
|
+
* the reader.
|
|
46
|
+
*/
|
|
47
|
+
export const DEFAULT_TOKENS_PER_CHAR_LATIN = 0.32;
|
|
48
|
+
export const DEFAULT_TOKENS_PER_CHAR_CJK = 0.85;
|
|
49
|
+
/** The Latin end under its original name: the fallback for a stream with no text. */
|
|
50
|
+
export const DEFAULT_TOKENS_PER_CHAR = DEFAULT_TOKENS_PER_CHAR_LATIN;
|
|
51
|
+
/** Calibration bounds: below the first a tokenizer is lying, above the second too. */
|
|
52
|
+
const MIN_TOKENS_PER_CHAR = 0.1;
|
|
53
|
+
const MAX_TOKENS_PER_CHAR = 1.5;
|
|
54
|
+
/** Weight of the newest settled step in the calibration average. */
|
|
55
|
+
const CALIBRATION_WEIGHT = 0.5;
|
|
56
|
+
/** Rates kept for the median; three is what the link chip's RTT median uses. */
|
|
57
|
+
const RATE_HISTORY = 3;
|
|
58
|
+
/**
|
|
59
|
+
* Characters to tokens, bounded and finite.
|
|
60
|
+
*
|
|
61
|
+
* The default is an average over mixed prose and code; every settled step
|
|
62
|
+
* replaces half of it with this session's own measurement, which is what makes
|
|
63
|
+
* the live estimate usable on CJK output (where one character is close to one
|
|
64
|
+
* token) without hardcoding a language.
|
|
65
|
+
*/
|
|
66
|
+
export function boundTokensPerChar(value, fallback = DEFAULT_TOKENS_PER_CHAR) {
|
|
67
|
+
if (!Number.isFinite(value) || value <= 0)
|
|
68
|
+
return fallback;
|
|
69
|
+
return Math.min(MAX_TOKENS_PER_CHAR, Math.max(MIN_TOKENS_PER_CHAR, value));
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The median of up to the last three rates, or the newest one alone.
|
|
73
|
+
*
|
|
74
|
+
* A single frame in which the terminal coalesced a burst of tokens into one
|
|
75
|
+
* delta reads as a spike; a median of three ignores it, while a real change
|
|
76
|
+
* survives because two agreeing samples decide it. Same rule, and the same
|
|
77
|
+
* reasoning, as the link chip's RTT median.
|
|
78
|
+
*/
|
|
79
|
+
export function medianRate(values) {
|
|
80
|
+
const recent = values.filter(value => Number.isFinite(value) && value > 0).slice(-RATE_HISTORY);
|
|
81
|
+
if (recent.length === 0)
|
|
82
|
+
return undefined;
|
|
83
|
+
const sorted = [...recent].sort((a, b) => a - b);
|
|
84
|
+
return sorted[Math.floor(sorted.length / 2)];
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Tracks one step's live decode rate from character deltas.
|
|
88
|
+
*
|
|
89
|
+
* Sampling is the caller's job (`note` per fragment): the window's own
|
|
90
|
+
* minimum span is what keeps the number stable, so a burst of tiny deltas and
|
|
91
|
+
* one coalesced delta produce the same answer.
|
|
92
|
+
*/
|
|
93
|
+
/**
|
|
94
|
+
* Whether one code point is written in a script whose characters are close to one
|
|
95
|
+
* token each: CJK ideographs, kana, Hangul syllables, and the fullwidth or CJK
|
|
96
|
+
* punctuation that travels with them.
|
|
97
|
+
*
|
|
98
|
+
* Deliberately a property of the *text* rather than of the reader's locale: a
|
|
99
|
+
* Chinese reader's English reply is Latin text and costs Latin tokens per
|
|
100
|
+
* character, and `~tok/s` has to describe the tokens, not the person.
|
|
101
|
+
*/
|
|
102
|
+
export function isCjkCodePoint(codePoint) {
|
|
103
|
+
return (codePoint >= 0x3040 && codePoint <= 0x30ff) // hiragana + katakana
|
|
104
|
+
|| (codePoint >= 0x3400 && codePoint <= 0x4dbf) // CJK extension A
|
|
105
|
+
|| (codePoint >= 0x4e00 && codePoint <= 0x9fff) // CJK unified ideographs
|
|
106
|
+
|| (codePoint >= 0xac00 && codePoint <= 0xd7af) // hangul syllables
|
|
107
|
+
|| (codePoint >= 0xf900 && codePoint <= 0xfaff) // CJK compatibility ideographs
|
|
108
|
+
|| (codePoint >= 0xff00 && codePoint <= 0xff60) // fullwidth forms
|
|
109
|
+
|| (codePoint >= 0x3000 && codePoint <= 0x303f) // CJK punctuation
|
|
110
|
+
|| (codePoint >= 0x20000 && codePoint <= 0x3fffd); // extensions B and up
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* The starting ratio for a stream of a given script mix: `cjkShare` of the
|
|
114
|
+
* characters read as CJK, the rest as Latin. Continuous by construction, so a
|
|
115
|
+
* reply that is 70% English and 30% Chinese lands 30% of the way up the range
|
|
116
|
+
* instead of being classified as one or the other.
|
|
117
|
+
*/
|
|
118
|
+
export function scriptTokensPerChar(cjkShare) {
|
|
119
|
+
const share = Number.isFinite(cjkShare) ? Math.min(1, Math.max(0, cjkShare)) : 0;
|
|
120
|
+
return DEFAULT_TOKENS_PER_CHAR_LATIN
|
|
121
|
+
+ (DEFAULT_TOKENS_PER_CHAR_CJK - DEFAULT_TOKENS_PER_CHAR_LATIN) * share;
|
|
122
|
+
}
|
|
123
|
+
export class ThroughputTracker {
|
|
124
|
+
/** The session's own ratio, once a step has settled. */
|
|
125
|
+
calibrated;
|
|
126
|
+
/** Characters and CJK characters seen in the step being estimated. */
|
|
127
|
+
observedChars = 0;
|
|
128
|
+
observedCjk = 0;
|
|
129
|
+
samples = [];
|
|
130
|
+
chars = 0;
|
|
131
|
+
baseAt;
|
|
132
|
+
startedAt;
|
|
133
|
+
lastDeltaAt = 0;
|
|
134
|
+
lastStepChars;
|
|
135
|
+
/** Rates already computed this step, kept for the median. */
|
|
136
|
+
recentRates = [];
|
|
137
|
+
lastRate;
|
|
138
|
+
settledRate;
|
|
139
|
+
/**
|
|
140
|
+
* The ratio the estimate is using right now: this session's own measurement once
|
|
141
|
+
* a step has settled, and the script mix of the stream being estimated before
|
|
142
|
+
* that. `/status` and the tests read it.
|
|
143
|
+
*/
|
|
144
|
+
tokensPerChar() {
|
|
145
|
+
return this.calibrated ?? scriptTokensPerChar(this.cjkShare());
|
|
146
|
+
}
|
|
147
|
+
/** Share of this step's characters that were CJK, 0 when none have arrived. */
|
|
148
|
+
cjkShare() {
|
|
149
|
+
return this.observedChars === 0 ? 0 : this.observedCjk / this.observedChars;
|
|
150
|
+
}
|
|
151
|
+
/** Whether the ratio is a settled measurement rather than a script estimate. */
|
|
152
|
+
calibratedRatio() {
|
|
153
|
+
return this.calibrated !== undefined;
|
|
154
|
+
}
|
|
155
|
+
/** Drop a finished step's stream state, keeping calibration and last settle. */
|
|
156
|
+
reset() {
|
|
157
|
+
this.samples = [];
|
|
158
|
+
this.chars = 0;
|
|
159
|
+
this.observedChars = 0;
|
|
160
|
+
this.observedCjk = 0;
|
|
161
|
+
this.baseAt = undefined;
|
|
162
|
+
this.startedAt = undefined;
|
|
163
|
+
this.lastDeltaAt = 0;
|
|
164
|
+
this.lastStepChars = undefined;
|
|
165
|
+
this.recentRates = [];
|
|
166
|
+
this.lastRate = undefined;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* One observed fragment of model output.
|
|
170
|
+
*
|
|
171
|
+
* The fragment itself, not a count: the estimate is tokens per character, and
|
|
172
|
+
* how many tokens a character is worth depends on which characters they are, so
|
|
173
|
+
* the script mix has to be measured here rather than assumed once for the
|
|
174
|
+
* session. Code points rather than UTF-16 units, so an emoji or a rare ideograph
|
|
175
|
+
* outside the BMP counts as the one character the reader sees.
|
|
176
|
+
* @param text - the fragment; empty is ignored.
|
|
177
|
+
* @param time - wall clock the fragment arrived at, for tests and replay.
|
|
178
|
+
*/
|
|
179
|
+
note(text, time = Date.now()) {
|
|
180
|
+
if (text === '')
|
|
181
|
+
return;
|
|
182
|
+
let chars = 0;
|
|
183
|
+
let cjk = 0;
|
|
184
|
+
for (const char of text) {
|
|
185
|
+
chars += 1;
|
|
186
|
+
if (isCjkCodePoint(char.codePointAt(0) ?? 0))
|
|
187
|
+
cjk += 1;
|
|
188
|
+
}
|
|
189
|
+
if (chars === 0)
|
|
190
|
+
return;
|
|
191
|
+
if (this.startedAt === undefined) {
|
|
192
|
+
this.startedAt = time;
|
|
193
|
+
this.baseAt = time;
|
|
194
|
+
}
|
|
195
|
+
this.chars += chars;
|
|
196
|
+
this.observedChars += chars;
|
|
197
|
+
this.observedCjk += cjk;
|
|
198
|
+
this.lastDeltaAt = time;
|
|
199
|
+
const at = time - (this.baseAt ?? time);
|
|
200
|
+
const last = this.samples[this.samples.length - 1];
|
|
201
|
+
if (last !== undefined && last.at === at) {
|
|
202
|
+
// Two fragments inside one millisecond: one sample, or the window fills
|
|
203
|
+
// with points that share a timestamp and the span stays zero.
|
|
204
|
+
last.chars = this.chars;
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
this.samples.push({ at, chars: this.chars });
|
|
208
|
+
}
|
|
209
|
+
this.trim(at);
|
|
210
|
+
this.sampleRate();
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Settle a step: fold its exact decode rate into the calibration and into the
|
|
214
|
+
* number an idle row shows.
|
|
215
|
+
* @param outputTokens - the step's authoritative output tokens.
|
|
216
|
+
* @param decodeMs - milliseconds from first token to the settled message.
|
|
217
|
+
*/
|
|
218
|
+
settle(outputTokens, decodeMs) {
|
|
219
|
+
const chars = this.chars;
|
|
220
|
+
if (Number.isFinite(outputTokens) && outputTokens > 0 && Number.isFinite(decodeMs) && decodeMs > 0) {
|
|
221
|
+
this.settledRate = outputTokens / (decodeMs / 1_000);
|
|
222
|
+
if (chars > 0) {
|
|
223
|
+
// The session's own measurement takes over from the script estimate here,
|
|
224
|
+
// and then keeps tracking it: half of the new sample, half of what was
|
|
225
|
+
// already known, so one unusual reply cannot redefine the session.
|
|
226
|
+
const before = this.tokensPerChar();
|
|
227
|
+
const observed = boundTokensPerChar(outputTokens / chars, before);
|
|
228
|
+
this.calibrated = before * (1 - CALIBRATION_WEIGHT) + observed * CALIBRATION_WEIGHT;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
this.reset();
|
|
232
|
+
}
|
|
233
|
+
/** What the status row paints, and what `/status` reports. */
|
|
234
|
+
view(now = Date.now()) {
|
|
235
|
+
const liveRate = this.lastRate;
|
|
236
|
+
const fresh = liveRate !== undefined
|
|
237
|
+
&& this.lastDeltaAt > 0
|
|
238
|
+
&& now - this.lastDeltaAt <= THROUGHPUT_FRESH_MS;
|
|
239
|
+
return {
|
|
240
|
+
...(this.settledRate === undefined ? {} : { settledRate: this.settledRate }),
|
|
241
|
+
liveChars: this.chars,
|
|
242
|
+
...(liveRate === undefined ? {} : { liveRate }),
|
|
243
|
+
fresh,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
/** Drop samples that fell out of the window, and keep the newest few. */
|
|
247
|
+
trim(at) {
|
|
248
|
+
const cutoff = at - THROUGHPUT_WINDOW_MS;
|
|
249
|
+
let drop = 0;
|
|
250
|
+
while (drop + 2 < this.samples.length && (this.samples[drop + 1]?.at ?? 0) <= cutoff)
|
|
251
|
+
drop += 1;
|
|
252
|
+
if (drop > 0)
|
|
253
|
+
this.samples.splice(0, drop);
|
|
254
|
+
if (this.samples.length > MAX_SAMPLES)
|
|
255
|
+
this.samples.splice(0, this.samples.length - MAX_SAMPLES);
|
|
256
|
+
}
|
|
257
|
+
/** Recompute the window's rate and fold it into the median buffer. */
|
|
258
|
+
sampleRate() {
|
|
259
|
+
const newest = this.samples[this.samples.length - 1];
|
|
260
|
+
if (newest === undefined)
|
|
261
|
+
return;
|
|
262
|
+
// The oldest sample still inside the minimum span, so a rate is never taken
|
|
263
|
+
// over a 20 ms sliver where one chunk decides the answer.
|
|
264
|
+
let oldest;
|
|
265
|
+
for (const sample of this.samples) {
|
|
266
|
+
if (newest.at - sample.at >= THROUGHPUT_MIN_SPAN_MS)
|
|
267
|
+
oldest = sample;
|
|
268
|
+
else
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
oldest ??= this.samples[0];
|
|
272
|
+
if (oldest === undefined)
|
|
273
|
+
return;
|
|
274
|
+
const span = newest.at - oldest.at;
|
|
275
|
+
const chars = newest.chars - oldest.chars;
|
|
276
|
+
if (span < THROUGHPUT_MIN_SPAN_MS || chars <= 0)
|
|
277
|
+
return;
|
|
278
|
+
// The ratio is sampled per rate, so a step that starts in English and drifts
|
|
279
|
+
// into Chinese follows the text instead of one classification for the whole
|
|
280
|
+
// reply.
|
|
281
|
+
const rate = chars / (span / 1_000) * this.tokensPerChar();
|
|
282
|
+
if (!Number.isFinite(rate) || rate <= 0)
|
|
283
|
+
return;
|
|
284
|
+
this.lastStepChars = this.chars;
|
|
285
|
+
this.recentRates.push(rate);
|
|
286
|
+
if (this.recentRates.length > RATE_HISTORY * 3)
|
|
287
|
+
this.recentRates.shift();
|
|
288
|
+
this.lastRate = medianRate(this.recentRates) ?? rate;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* A rate as the row paints it: whole numbers, `<1` rather than nothing.
|
|
293
|
+
*
|
|
294
|
+
* A slow step that decoded 3 tokens over 30 seconds is still decoding; rounding
|
|
295
|
+
* it to `0` reads as a stalled model. Above 1000 the number is compacted, since
|
|
296
|
+
* four digits is cells the quota and context meters want more.
|
|
297
|
+
*/
|
|
298
|
+
export function formatRate(tokensPerSecond) {
|
|
299
|
+
if (!Number.isFinite(tokensPerSecond) || tokensPerSecond < 1)
|
|
300
|
+
return '<1';
|
|
301
|
+
if (tokensPerSecond >= 1_000)
|
|
302
|
+
return `${(Math.round(tokensPerSecond / 10) / 100).toFixed(2)}K`;
|
|
303
|
+
return String(Math.round(tokensPerSecond));
|
|
304
|
+
}
|
|
305
|
+
//# sourceMappingURL=throughput.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"throughput.js","sourceRoot":"","sources":["../src/throughput.ts"],"names":[],"mappings":"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"}
|
package/lib/tool-present.js
CHANGED
|
@@ -8,6 +8,7 @@ import { padToWidth, sliceCodePoints, truncate, wrap } from './term-text.js';
|
|
|
8
8
|
import { firstString, parseJsonArgs, scalarText } from './json-args.js';
|
|
9
9
|
import { parsePlanTodos, planTitleFromMarkdown, todoProgressBar, todoProgressLabel, todoSummary, askSummary, TODO_STATUS_MARK, todoItemKind, planMarkdownFromArgs, } from './plan.js';
|
|
10
10
|
import { activeTheme, themeExtraToken, themeToken } from './theme.js';
|
|
11
|
+
import { approvalProvenanceKey, approvalStateKey } from './approval-state.js';
|
|
11
12
|
export const SHELL_TOOL_NAMES = new Set(['bash', 'pwsh']);
|
|
12
13
|
export const DIFF_TOOL_NAMES = new Set(['edit', 'write', 'str_replace_editor']);
|
|
13
14
|
export const JOB_TOOL_NAMES = new Set(['job_list', 'job_output', 'job_kill']);
|
|
@@ -246,6 +247,26 @@ export const SUBAGENT_TOOL_NAMES = new Set(['subagent', 'subagent_fork', 'task']
|
|
|
246
247
|
* just duplicates chrome.
|
|
247
248
|
*/
|
|
248
249
|
export const HIDDEN_TOOL_NAMES = new Set(['get_goal']);
|
|
250
|
+
/**
|
|
251
|
+
* Tools whose card is the *question* they asked, not a generic tool card (B2.4).
|
|
252
|
+
*
|
|
253
|
+
* `ask_user_question` is the only one: its durable representation is the question
|
|
254
|
+
* card built from the call's arguments, and drawing both was the duplicate this
|
|
255
|
+
* round removes. It is a separate set from {@link HIDDEN_TOOL_NAMES} on purpose —
|
|
256
|
+
* hidden means "no representation at all" (`get_goal`), while this means "the
|
|
257
|
+
* representation is drawn by the question projection". A tool card that never
|
|
258
|
+
* appears is a *different* decision from one whose content is shown elsewhere, and
|
|
259
|
+
* the audit has to be able to tell them apart.
|
|
260
|
+
*/
|
|
261
|
+
export const QUESTION_TOOL_NAMES = new Set(['ask_user_question']);
|
|
262
|
+
/**
|
|
263
|
+
* Tools whose card is the *artifact* they act on, not a tool card of their own.
|
|
264
|
+
*
|
|
265
|
+
* `exit_plan_mode` is the plan review: the plan artifact carries the reviewed body,
|
|
266
|
+
* the review's outcome and the lifecycle lines, so a generic card beside all of that
|
|
267
|
+
* was a third representation of one event (B2.5 §15).
|
|
268
|
+
*/
|
|
269
|
+
export const PLAN_TOOL_NAMES = new Set(['exit_plan_mode']);
|
|
249
270
|
const TOOL_TITLE_KEYS = [
|
|
250
271
|
'edit', 'write', 'str_replace_editor', 'fetch', 'list_files', 'list', 'ls',
|
|
251
272
|
'find', 'search', 'delete', 'rm', 'rename', 'mv', 'mkdir', 'skills', 'skill',
|
|
@@ -470,15 +491,23 @@ export function buildToolHeader(input) {
|
|
|
470
491
|
const flipping = input.flipping === true;
|
|
471
492
|
const marker = flipping ? '◇' : input.expanded ? '▾' : '▸';
|
|
472
493
|
const lead = `${prefix}${marker} ● ${input.title}`;
|
|
473
|
-
|
|
494
|
+
// The summary is a *row*: a `bash` call hands over the command as written, so a
|
|
495
|
+
// heredoc arrives with its newlines intact and would break the frame's absolute
|
|
496
|
+
// row addressing (see `truncateToWidth`). Collapsed here rather than at paint time
|
|
497
|
+
// so the colour offsets below are computed against the text that is actually
|
|
498
|
+
// drawn.
|
|
499
|
+
const summary = input.summary.replace(/\s+/gu, ' ').trim();
|
|
500
|
+
const summaryText = summary === '' ? '' : ` ${summary}`;
|
|
474
501
|
const statToken = input.diffStat === undefined ? '' : diffStatToken(input.diffStat.add, input.diffStat.del);
|
|
475
502
|
const statText = statToken === '' ? '' : ` ${statToken}`;
|
|
476
503
|
const stateGap = stateToken === '' ? '' : ' ';
|
|
477
504
|
const tail = `${stateGap}${stateToken}${exit}${spinner}`;
|
|
478
|
-
const
|
|
505
|
+
const approvalChip = input.approval === undefined ? '' : approvalChipText(input.approval);
|
|
506
|
+
const approvalText = approvalChip === '' ? '' : ` ${approvalChip}`;
|
|
507
|
+
const plain = `${lead}${approvalText}${summaryText}${statText}${tail}`;
|
|
479
508
|
const stateCode = toolStateColor(input.status);
|
|
480
509
|
const dotIndex = lead.indexOf('●');
|
|
481
|
-
const stateIndex = stateToken === '' ? -1 : lead.length + summaryText.length + statText.length + stateGap.length;
|
|
510
|
+
const stateIndex = stateToken === '' ? -1 : lead.length + approvalText.length + summaryText.length + statText.length + stateGap.length;
|
|
482
511
|
const segments = [];
|
|
483
512
|
if (flipping) {
|
|
484
513
|
const markerIndex = prefix.length;
|
|
@@ -486,13 +515,20 @@ export function buildToolHeader(input) {
|
|
|
486
515
|
}
|
|
487
516
|
if (dotIndex >= 0)
|
|
488
517
|
segments.push({ start: dotIndex, end: dotIndex + '●'.length, sgr: stateCode });
|
|
518
|
+
if (approvalText.length > 0) {
|
|
519
|
+
segments.push({
|
|
520
|
+
start: lead.length,
|
|
521
|
+
end: lead.length + approvalText.length,
|
|
522
|
+
sgr: themeExtraToken(activeTheme(), input.approval?.state === 'rejected' ? 'accent' : 'tool-ok'),
|
|
523
|
+
});
|
|
524
|
+
}
|
|
489
525
|
if (summaryText.length > 0) {
|
|
490
|
-
segments.push({ start: lead.length, end: lead.length + summaryText.length, sgr: themeToken(activeTheme(), 'system') });
|
|
526
|
+
segments.push({ start: lead.length + approvalText.length, end: lead.length + approvalText.length + summaryText.length, sgr: themeToken(activeTheme(), 'system') });
|
|
491
527
|
}
|
|
492
528
|
if (statToken !== '') {
|
|
493
529
|
// Git diffstat colors: deletions red, additions green. The token sits
|
|
494
530
|
// two cells after the summary, deletions before the joining space.
|
|
495
|
-
const statStart = lead.length + summaryText.length + 2;
|
|
531
|
+
const statStart = lead.length + approvalText.length + summaryText.length + 2;
|
|
496
532
|
const delEnd = statToken.indexOf(' +');
|
|
497
533
|
if (statToken.startsWith('-')) {
|
|
498
534
|
segments.push({
|
|
@@ -510,11 +546,11 @@ export function buildToolHeader(input) {
|
|
|
510
546
|
segments.push({ start: stateIndex, end: stateIndex + stateToken.length + exit.length, sgr: stateCode });
|
|
511
547
|
}
|
|
512
548
|
else if (exit !== '') {
|
|
513
|
-
const exitIndex = lead.length + summaryText.length + statText.length;
|
|
549
|
+
const exitIndex = lead.length + approvalText.length + summaryText.length + statText.length;
|
|
514
550
|
segments.push({ start: exitIndex, end: exitIndex + exit.length, sgr: stateCode });
|
|
515
551
|
}
|
|
516
552
|
if (spinner !== '') {
|
|
517
|
-
const spinnerStart = (stateIndex >= 0 ? stateIndex + stateToken.length + exit.length : lead.length + summaryText.length + statText.length + exit.length);
|
|
553
|
+
const spinnerStart = (stateIndex >= 0 ? stateIndex + stateToken.length + exit.length : lead.length + approvalText.length + summaryText.length + statText.length + exit.length);
|
|
518
554
|
segments.push({
|
|
519
555
|
start: spinnerStart,
|
|
520
556
|
end: plain.length,
|
|
@@ -523,6 +559,20 @@ export function buildToolHeader(input) {
|
|
|
523
559
|
}
|
|
524
560
|
return { plain, segments: segments.filter(segment => segment.end > segment.start) };
|
|
525
561
|
}
|
|
562
|
+
/**
|
|
563
|
+
* The title chip: the state, and — when the reader is being told something
|
|
564
|
+
* weaker — why they can trust it less.
|
|
565
|
+
*
|
|
566
|
+
* `live` and `durable` are the same fact at two moments (a person decided, and
|
|
567
|
+
* the log still says so), so they print the state alone. `policy` adds `auto`,
|
|
568
|
+
* and `inferred` says so: a resume that found only the Harness's refusal sentence
|
|
569
|
+
* in a tool result is a reading, and the card has to admit it.
|
|
570
|
+
*/
|
|
571
|
+
export function approvalChipText(approval) {
|
|
572
|
+
const state = t(approvalStateKey(approval.state));
|
|
573
|
+
const note = approvalProvenanceKey(approval);
|
|
574
|
+
return note === undefined ? state : `${state}·${t(note)}`;
|
|
575
|
+
}
|
|
526
576
|
/** How many terminal rows a tool body occupies after wrapping. */
|
|
527
577
|
export function wrappedToolBodyLineCount(lines, width) {
|
|
528
578
|
const inner = Math.max(1, width - 2);
|