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
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Representation policy: the one place that decides what a transcript line *is*.
|
|
3
|
+
*
|
|
4
|
+
* AD-10 in `docs/decisions/b2-architecture-decisions.md` froze the shape —
|
|
5
|
+
* `Event / Command → Representation Policy → Representation → Renderer` — and this
|
|
6
|
+
* module is the policy. A renderer never decides whether something exists, and a
|
|
7
|
+
* creation site never decides whether its line is history or an echo: it names its
|
|
8
|
+
* *source*, and the table below says what that source means.
|
|
9
|
+
*
|
|
10
|
+
* The boundary is enforced by the type system rather than by review:
|
|
11
|
+
*
|
|
12
|
+
* - `pushRow` takes a `Representation`, not a `Row`, so a line cannot reach the
|
|
13
|
+
* transcript without passing through `represent()` — a bare row is a type error;
|
|
14
|
+
* - `RepresentationSource` is `keyof` the policy table, so naming a source that has
|
|
15
|
+
* no policy is also a type error: adding a creation site *requires* adding its
|
|
16
|
+
* policy, and there is no default to fall back on;
|
|
17
|
+
* - anything that arrives anyway (a JavaScript caller, a test fixture) is pushed
|
|
18
|
+
* with no metadata and counted as **unclassified** by `auditRepresentations`,
|
|
19
|
+
* which the suite asserts is zero. Nothing is silently assumed durable.
|
|
20
|
+
*
|
|
21
|
+
* Three durabilities (B2.3a):
|
|
22
|
+
*
|
|
23
|
+
* - `durable` — rebuilt from the authoritative session log by replaying it, so it
|
|
24
|
+
* is there again after a resume;
|
|
25
|
+
* - `display` — lives only for this Host/display's lifetime; its absence after a
|
|
26
|
+
* resume is correct, not a loss;
|
|
27
|
+
* - `live` — a runtime projection, recomputed every tick. **It must never be a
|
|
28
|
+
* source row at all** (B2.2 moved the streaming text and the wait card out); a
|
|
29
|
+
* `live` row in the transcript is a bug, and the audit reports it as one.
|
|
30
|
+
*
|
|
31
|
+
* The A/B/C/D classes are orthogonal and describe the *form* of the
|
|
32
|
+
* representation, not its importance:
|
|
33
|
+
*
|
|
34
|
+
* - **A** persistent transcript entry — an ordinary row;
|
|
35
|
+
* - **B** ephemeral transient — never a source row (the live tail, a Surface);
|
|
36
|
+
* - **C** summary entry — a row that summarizes an exchange (`question` cards);
|
|
37
|
+
* - **D** artifact reference — a row that points at an object (plans, later).
|
|
38
|
+
*
|
|
39
|
+
* "Important" is not the same as "durable": a quota alert matters and is still
|
|
40
|
+
* `display`, because no replay can bring it back.
|
|
41
|
+
*/
|
|
42
|
+
import type { Row } from './transcript-types.js';
|
|
43
|
+
/** Where a line can come from after a resume. */
|
|
44
|
+
export type Durability = 'durable' | 'display' | 'live';
|
|
45
|
+
/**
|
|
46
|
+
* Where a representation is *shown* (B2.3b's routing boundary).
|
|
47
|
+
*
|
|
48
|
+
* Classification says what a line is; routing says where it goes. The policy owns
|
|
49
|
+
* both, so a control-plane confirmation cannot be a transcript row at one call site
|
|
50
|
+
* and a footer echo at the next: the source decides, once.
|
|
51
|
+
*
|
|
52
|
+
* - `transcript` — a real line in the log's view (narrative, a durable summary, a
|
|
53
|
+
* Screen's line-mode echo, or a local record with causal value);
|
|
54
|
+
* - `echo` — a Host-local footer chip: the confirmation of something the reader just
|
|
55
|
+
* did. Replaced by the next echo, cleared by the next submit, never in the log;
|
|
56
|
+
* - `notice` — a one-row Host-local notice above the composer, for feedback that
|
|
57
|
+
* needs to be read now but is not history (a failed command, an operational
|
|
58
|
+
* warning). Same lifetime rules as the echo, more room for the message.
|
|
59
|
+
*
|
|
60
|
+
* The rule behind the split is AD-13: narrative and causal facts go to the
|
|
61
|
+
* transcript, runtime state belongs to the footer's own facts, and immediate action
|
|
62
|
+
* feedback is an echo or a notice.
|
|
63
|
+
*/
|
|
64
|
+
export type Destination = 'transcript' | 'echo' | 'notice';
|
|
65
|
+
/** The form of the representation (see the module comment). */
|
|
66
|
+
export type RepresentationClass = 'A' | 'B' | 'C' | 'D';
|
|
67
|
+
/**
|
|
68
|
+
* Every place that may put a line into the transcript.
|
|
69
|
+
*
|
|
70
|
+
* Adding an entry here is what makes a new creation site possible, and the entry
|
|
71
|
+
* states what the line *is*. The ids name the producer, not the wording: one id
|
|
72
|
+
* covers a family of messages whose answer to "is this history?" is the same.
|
|
73
|
+
*/
|
|
74
|
+
export declare const REPRESENTATION_POLICY: {
|
|
75
|
+
readonly 'user-message': {
|
|
76
|
+
readonly durability: "durable";
|
|
77
|
+
readonly class: "A";
|
|
78
|
+
readonly destination: "transcript";
|
|
79
|
+
};
|
|
80
|
+
readonly 'assistant-message': {
|
|
81
|
+
readonly durability: "durable";
|
|
82
|
+
readonly class: "A";
|
|
83
|
+
readonly destination: "transcript";
|
|
84
|
+
};
|
|
85
|
+
readonly 'reasoning-message': {
|
|
86
|
+
readonly durability: "durable";
|
|
87
|
+
readonly class: "A";
|
|
88
|
+
readonly destination: "transcript";
|
|
89
|
+
};
|
|
90
|
+
readonly 'tool-call': {
|
|
91
|
+
readonly durability: "durable";
|
|
92
|
+
readonly class: "A";
|
|
93
|
+
readonly destination: "transcript";
|
|
94
|
+
};
|
|
95
|
+
readonly 'turn-error': {
|
|
96
|
+
readonly durability: "durable";
|
|
97
|
+
readonly class: "A";
|
|
98
|
+
readonly destination: "transcript";
|
|
99
|
+
};
|
|
100
|
+
/** Notices the log itself carries: reminders, context snapshots, interrupted streams. */
|
|
101
|
+
readonly 'session-notice': {
|
|
102
|
+
readonly durability: "durable";
|
|
103
|
+
readonly class: "A";
|
|
104
|
+
readonly destination: "transcript";
|
|
105
|
+
};
|
|
106
|
+
readonly compaction: {
|
|
107
|
+
readonly durability: "durable";
|
|
108
|
+
readonly class: "A";
|
|
109
|
+
readonly destination: "transcript";
|
|
110
|
+
};
|
|
111
|
+
readonly goal: {
|
|
112
|
+
readonly durability: "durable";
|
|
113
|
+
readonly class: "A";
|
|
114
|
+
readonly destination: "transcript";
|
|
115
|
+
};
|
|
116
|
+
readonly 'prompt-injection': {
|
|
117
|
+
readonly durability: "durable";
|
|
118
|
+
readonly class: "A";
|
|
119
|
+
readonly destination: "transcript";
|
|
120
|
+
};
|
|
121
|
+
readonly 'plan-row': {
|
|
122
|
+
readonly durability: "durable";
|
|
123
|
+
readonly class: "D";
|
|
124
|
+
readonly destination: "transcript";
|
|
125
|
+
};
|
|
126
|
+
readonly 'subagent-card': {
|
|
127
|
+
readonly durability: "durable";
|
|
128
|
+
readonly class: "A";
|
|
129
|
+
readonly destination: "transcript";
|
|
130
|
+
};
|
|
131
|
+
readonly 'command-event': {
|
|
132
|
+
readonly durability: "durable";
|
|
133
|
+
readonly class: "A";
|
|
134
|
+
readonly destination: "transcript";
|
|
135
|
+
};
|
|
136
|
+
readonly 'retry-notice': {
|
|
137
|
+
readonly durability: "durable";
|
|
138
|
+
readonly class: "A";
|
|
139
|
+
readonly destination: "transcript";
|
|
140
|
+
};
|
|
141
|
+
readonly 'session-route': {
|
|
142
|
+
readonly durability: "durable";
|
|
143
|
+
readonly class: "A";
|
|
144
|
+
readonly destination: "transcript";
|
|
145
|
+
};
|
|
146
|
+
/** The question card: one row summarizing an exchange, rebuilt from the projection. */
|
|
147
|
+
readonly 'question-card': {
|
|
148
|
+
readonly durability: "durable";
|
|
149
|
+
readonly class: "C";
|
|
150
|
+
readonly destination: "transcript";
|
|
151
|
+
};
|
|
152
|
+
readonly boot: {
|
|
153
|
+
readonly durability: "display";
|
|
154
|
+
readonly class: "A";
|
|
155
|
+
readonly destination: "transcript";
|
|
156
|
+
};
|
|
157
|
+
/** A confirmation of something the reader just did: a footer echo. */
|
|
158
|
+
readonly 'command-feedback': {
|
|
159
|
+
readonly durability: "display";
|
|
160
|
+
readonly class: "A";
|
|
161
|
+
readonly destination: "echo";
|
|
162
|
+
};
|
|
163
|
+
/**
|
|
164
|
+
* A command that *reports* state rather than changing it (`/retryauth` with no
|
|
165
|
+
* argument, `/notify` showing its target).
|
|
166
|
+
*
|
|
167
|
+
* A report is information, not an acknowledgement: the reader may need to read it
|
|
168
|
+
* twice, or compare it with something they scrolled past, so it stays in the
|
|
169
|
+
* transcript. The policy draws that line, not the call site.
|
|
170
|
+
*/
|
|
171
|
+
readonly 'command-status': {
|
|
172
|
+
readonly durability: "display";
|
|
173
|
+
readonly class: "A";
|
|
174
|
+
readonly destination: "transcript";
|
|
175
|
+
};
|
|
176
|
+
/** The outcome of a repair that edited the install (`/mode fix`). */
|
|
177
|
+
readonly 'repair-result': {
|
|
178
|
+
readonly durability: "display";
|
|
179
|
+
readonly class: "A";
|
|
180
|
+
readonly destination: "transcript";
|
|
181
|
+
};
|
|
182
|
+
/**
|
|
183
|
+
* A local command that failed.
|
|
184
|
+
*
|
|
185
|
+
* Kept in the transcript: the message is usually an error chain the reader has to
|
|
186
|
+
* be able to scroll back to, and re-running the command is not the same as reading
|
|
187
|
+
* what it said. (A command that was simply *misused* is the notice below.)
|
|
188
|
+
*/
|
|
189
|
+
readonly 'command-error': {
|
|
190
|
+
readonly durability: "display";
|
|
191
|
+
readonly class: "A";
|
|
192
|
+
readonly destination: "transcript";
|
|
193
|
+
};
|
|
194
|
+
/**
|
|
195
|
+
* A command that could not be understood at all: a mistyped name, a missing
|
|
196
|
+
* argument, a value outside the table. Immediate feedback, and nothing to keep —
|
|
197
|
+
* the reader fixes the line and moves on.
|
|
198
|
+
*/
|
|
199
|
+
readonly 'command-misuse': {
|
|
200
|
+
readonly durability: "display";
|
|
201
|
+
readonly class: "A";
|
|
202
|
+
readonly destination: "notice";
|
|
203
|
+
};
|
|
204
|
+
/** How to use a command: the reader asked for it, and it is not history. */
|
|
205
|
+
readonly 'command-usage': {
|
|
206
|
+
readonly durability: "display";
|
|
207
|
+
readonly class: "A";
|
|
208
|
+
readonly destination: "notice";
|
|
209
|
+
};
|
|
210
|
+
readonly 'auth-notice': {
|
|
211
|
+
readonly durability: "display";
|
|
212
|
+
readonly class: "A";
|
|
213
|
+
readonly destination: "transcript";
|
|
214
|
+
};
|
|
215
|
+
readonly onboarding: {
|
|
216
|
+
readonly durability: "display";
|
|
217
|
+
readonly class: "A";
|
|
218
|
+
readonly destination: "transcript";
|
|
219
|
+
};
|
|
220
|
+
/**
|
|
221
|
+
* An auto approval the TUI *refused*. The transient notice, not a row (B2.4).
|
|
222
|
+
*
|
|
223
|
+
* The durable trace of a refusal is the tool card it guarded — its `approval`
|
|
224
|
+
* field, rebuilt from the Harness's own `approval/asked` + `approval/decided`
|
|
225
|
+
* pair — so a second, permanent sentence saying "rejected" would be a second
|
|
226
|
+
* representation of the same event (and one nothing can rebuild). What the notice
|
|
227
|
+
* adds is *now*: the reader sees why the model changed course while it happens.
|
|
228
|
+
*/
|
|
229
|
+
readonly 'approval-notice': {
|
|
230
|
+
readonly durability: "display";
|
|
231
|
+
readonly class: "A";
|
|
232
|
+
readonly destination: "notice";
|
|
233
|
+
};
|
|
234
|
+
/**
|
|
235
|
+
* An auto approval the TUI *granted*.
|
|
236
|
+
*
|
|
237
|
+
* Routed to the footer chip rather than the notice row: an allow is an
|
|
238
|
+
* acknowledgement, and it happens far more often than a refusal — the identity
|
|
239
|
+
* row is for the one the reader must not miss.
|
|
240
|
+
*/
|
|
241
|
+
readonly 'approval-allowance': {
|
|
242
|
+
readonly durability: "display";
|
|
243
|
+
readonly class: "A";
|
|
244
|
+
readonly destination: "echo";
|
|
245
|
+
};
|
|
246
|
+
readonly 'doctor-result': {
|
|
247
|
+
readonly durability: "display";
|
|
248
|
+
readonly class: "A";
|
|
249
|
+
readonly destination: "transcript";
|
|
250
|
+
};
|
|
251
|
+
/** Preset wizard/list feedback and its failures. */
|
|
252
|
+
readonly 'preset-feedback': {
|
|
253
|
+
readonly durability: "display";
|
|
254
|
+
readonly class: "A";
|
|
255
|
+
readonly destination: "echo";
|
|
256
|
+
};
|
|
257
|
+
readonly 'preset-error': {
|
|
258
|
+
readonly durability: "display";
|
|
259
|
+
readonly class: "A";
|
|
260
|
+
readonly destination: "transcript";
|
|
261
|
+
};
|
|
262
|
+
/** The self-update path: progress is an echo, a failure is a notice. */
|
|
263
|
+
readonly 'update-feedback': {
|
|
264
|
+
readonly durability: "display";
|
|
265
|
+
readonly class: "A";
|
|
266
|
+
readonly destination: "echo";
|
|
267
|
+
};
|
|
268
|
+
readonly 'update-error': {
|
|
269
|
+
readonly durability: "display";
|
|
270
|
+
readonly class: "A";
|
|
271
|
+
readonly destination: "notice";
|
|
272
|
+
};
|
|
273
|
+
/** A quota or balance reading the reader asked for. */
|
|
274
|
+
readonly 'quota-report': {
|
|
275
|
+
readonly durability: "display";
|
|
276
|
+
readonly class: "A";
|
|
277
|
+
readonly destination: "echo";
|
|
278
|
+
};
|
|
279
|
+
/** The context window filling up: operational, and worth reading now. */
|
|
280
|
+
readonly 'runtime-warning': {
|
|
281
|
+
readonly durability: "display";
|
|
282
|
+
readonly class: "A";
|
|
283
|
+
readonly destination: "transcript";
|
|
284
|
+
};
|
|
285
|
+
/** An approval the TUI could not resolve on its own. */
|
|
286
|
+
readonly 'approval-warning': {
|
|
287
|
+
readonly durability: "display";
|
|
288
|
+
readonly class: "A";
|
|
289
|
+
readonly destination: "transcript";
|
|
290
|
+
};
|
|
291
|
+
/** A question the Session still holds but cannot answer any more. */
|
|
292
|
+
readonly 'question-notice': {
|
|
293
|
+
readonly durability: "display";
|
|
294
|
+
readonly class: "A";
|
|
295
|
+
readonly destination: "transcript";
|
|
296
|
+
};
|
|
297
|
+
/**
|
|
298
|
+
* A child agent starting or ending.
|
|
299
|
+
*
|
|
300
|
+
* Left in the transcript on purpose: it is narrative about work that happened, and
|
|
301
|
+
* the durable child *card* is what a reader scrolls back to.
|
|
302
|
+
*/
|
|
303
|
+
readonly 'subagent-notice': {
|
|
304
|
+
readonly durability: "display";
|
|
305
|
+
readonly class: "A";
|
|
306
|
+
readonly destination: "transcript";
|
|
307
|
+
};
|
|
308
|
+
/**
|
|
309
|
+
* What happened while the display was away.
|
|
310
|
+
*
|
|
311
|
+
* AD-13's causal exception: a gap in the work is exactly what a later reader needs
|
|
312
|
+
* explained, so this one stays in the transcript rather than becoming an echo.
|
|
313
|
+
*/
|
|
314
|
+
readonly 'away-summary': {
|
|
315
|
+
readonly durability: "display";
|
|
316
|
+
readonly class: "A";
|
|
317
|
+
readonly destination: "transcript";
|
|
318
|
+
};
|
|
319
|
+
readonly 'attach-notice': {
|
|
320
|
+
readonly durability: "display";
|
|
321
|
+
readonly class: "A";
|
|
322
|
+
readonly destination: "transcript";
|
|
323
|
+
};
|
|
324
|
+
readonly 'find-feedback': {
|
|
325
|
+
readonly durability: "display";
|
|
326
|
+
readonly class: "A";
|
|
327
|
+
readonly destination: "echo";
|
|
328
|
+
};
|
|
329
|
+
readonly 'copy-feedback': {
|
|
330
|
+
readonly durability: "display";
|
|
331
|
+
readonly class: "A";
|
|
332
|
+
readonly destination: "echo";
|
|
333
|
+
};
|
|
334
|
+
/**
|
|
335
|
+
* The caveat that a terminal cannot take an OSC 52 write, so the copy may not have
|
|
336
|
+
* landed.
|
|
337
|
+
*
|
|
338
|
+
* An operational warning (AD-13), not an acknowledgement: it goes to the notice row
|
|
339
|
+
* where it can be read, and stays out of the footer chip that says "copied" — the
|
|
340
|
+
* two would otherwise overwrite each other.
|
|
341
|
+
*/
|
|
342
|
+
readonly 'copy-caveat': {
|
|
343
|
+
readonly durability: "display";
|
|
344
|
+
readonly class: "A";
|
|
345
|
+
readonly destination: "notice";
|
|
346
|
+
};
|
|
347
|
+
readonly 'plan-notice': {
|
|
348
|
+
readonly durability: "display";
|
|
349
|
+
readonly class: "A";
|
|
350
|
+
readonly destination: "echo";
|
|
351
|
+
};
|
|
352
|
+
/**
|
|
353
|
+
* A plan artifact's lifecycle in the transcript: created, a review's answer, the
|
|
354
|
+
* mode left. The *reference* the reader scrolls back to (`plan-row` points at the
|
|
355
|
+
* same artifact), and the only representation of a transition — the plan body lives
|
|
356
|
+
* with the artifact, not in a second copy.
|
|
357
|
+
*/
|
|
358
|
+
readonly 'plan-lifecycle': {
|
|
359
|
+
readonly durability: "durable";
|
|
360
|
+
readonly class: "D";
|
|
361
|
+
readonly destination: "transcript";
|
|
362
|
+
};
|
|
363
|
+
/**
|
|
364
|
+
* A message the reader submitted while a turn was running.
|
|
365
|
+
*
|
|
366
|
+
* The record of it is the `user/message` the Harness appends when the next step
|
|
367
|
+
* claims it — that row is the durable representation. This one is the
|
|
368
|
+
* acknowledgement ("accepted, applies at the next step"), which has to be seen
|
|
369
|
+
* the moment it happens and is worth no geometry.
|
|
370
|
+
*/
|
|
371
|
+
readonly 'steer-notice': {
|
|
372
|
+
readonly durability: "display";
|
|
373
|
+
readonly class: "A";
|
|
374
|
+
readonly destination: "notice";
|
|
375
|
+
};
|
|
376
|
+
readonly 'error-surface': {
|
|
377
|
+
readonly durability: "display";
|
|
378
|
+
readonly class: "A";
|
|
379
|
+
readonly destination: "notice";
|
|
380
|
+
};
|
|
381
|
+
/** Line mode's echo of a Screen / Surface — there is no frame there to show it. */
|
|
382
|
+
readonly 'report-echo': {
|
|
383
|
+
readonly durability: "display";
|
|
384
|
+
readonly class: "A";
|
|
385
|
+
readonly destination: "transcript";
|
|
386
|
+
};
|
|
387
|
+
readonly 'surface-echo': {
|
|
388
|
+
readonly durability: "display";
|
|
389
|
+
readonly class: "A";
|
|
390
|
+
readonly destination: "transcript";
|
|
391
|
+
};
|
|
392
|
+
/**
|
|
393
|
+
* The workspace-changes card.
|
|
394
|
+
*
|
|
395
|
+
* The canonical `display` case (B2.3a): it *looks* like history — a card about a
|
|
396
|
+
* turn that happened — but the summary is not in the session log and a restarted
|
|
397
|
+
* Host cannot reopen it, so it must not pretend to survive a resume.
|
|
398
|
+
*/
|
|
399
|
+
readonly 'changes-card': {
|
|
400
|
+
readonly durability: "display";
|
|
401
|
+
readonly class: "A";
|
|
402
|
+
readonly destination: "transcript";
|
|
403
|
+
};
|
|
404
|
+
/** Rows a test fixture puts in, so the suite does not have to fabricate a policy. */
|
|
405
|
+
readonly fixture: {
|
|
406
|
+
readonly durability: "display";
|
|
407
|
+
readonly class: "A";
|
|
408
|
+
readonly destination: "transcript";
|
|
409
|
+
};
|
|
410
|
+
};
|
|
411
|
+
/** A source id that has a policy. Its type *is* the table's keys. */
|
|
412
|
+
export type RepresentationSource = keyof typeof REPRESENTATION_POLICY;
|
|
413
|
+
/** What a row carries once it has been through the policy. */
|
|
414
|
+
export interface RepresentationMeta {
|
|
415
|
+
source: RepresentationSource;
|
|
416
|
+
durability: Durability;
|
|
417
|
+
representationClass: RepresentationClass;
|
|
418
|
+
/** Where this representation is shown — a transcript row, an echo or a notice. */
|
|
419
|
+
destination: Destination;
|
|
420
|
+
}
|
|
421
|
+
/** A row plus the policy that produced it. The only thing `pushRow` accepts. */
|
|
422
|
+
export interface Representation {
|
|
423
|
+
readonly meta: RepresentationMeta;
|
|
424
|
+
readonly row: Row;
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Classify one row.
|
|
428
|
+
*
|
|
429
|
+
* The only constructor of a `Representation`: it looks the source up in the table,
|
|
430
|
+
* so the durability and class can never disagree with the policy, and a source with
|
|
431
|
+
* no entry cannot be named (the parameter's type is the table's keys).
|
|
432
|
+
*/
|
|
433
|
+
export declare function represent(source: RepresentationSource, row: Row): Representation;
|
|
434
|
+
/** Whether a value really is a classified representation (a runtime guard for JS callers). */
|
|
435
|
+
export declare function isRepresentation(value: unknown): value is Representation;
|
|
436
|
+
export interface RepresentationAudit {
|
|
437
|
+
/** Rows carrying a policy, by durability. */
|
|
438
|
+
counts: Record<Durability, number>;
|
|
439
|
+
/** Rows with no `representation` at all: must be 0. */
|
|
440
|
+
unclassified: number;
|
|
441
|
+
/**
|
|
442
|
+
* Rows claiming `live`: must be 0 — a live projection is not a source row.
|
|
443
|
+
*
|
|
444
|
+
* Counted from the row's own metadata, so a row whose source is not even in the
|
|
445
|
+
* table (and is therefore also `unclassified`) still reports the live violation.
|
|
446
|
+
*/
|
|
447
|
+
liveRows: number;
|
|
448
|
+
/** Per source: durability, class, destination and how many rows carry it. */
|
|
449
|
+
sources: {
|
|
450
|
+
source: string;
|
|
451
|
+
durability: Durability;
|
|
452
|
+
representationClass: RepresentationClass;
|
|
453
|
+
destination: Destination;
|
|
454
|
+
rows: number;
|
|
455
|
+
}[];
|
|
456
|
+
/** Rows by destination. `unknown` must be 0: every source has a route. */
|
|
457
|
+
destinations: Record<Destination, number>;
|
|
458
|
+
/** Sources with no destination — impossible by construction, and asserted anyway. */
|
|
459
|
+
unknownDestination: number;
|
|
460
|
+
/**
|
|
461
|
+
* Sources the policy knows, with no rows in this transcript. Named so a caller can
|
|
462
|
+
* see the *whole* policy rather than only the part that happened to be exercised.
|
|
463
|
+
*/
|
|
464
|
+
unusedSources: string[];
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Read a transcript's representations back.
|
|
468
|
+
*
|
|
469
|
+
* This is the audit artifact: it answers, for a real transcript, how many lines are
|
|
470
|
+
* history, how many are this Host's own voice, and how many nobody classified. The
|
|
471
|
+
* `live` and `unclassified` numbers are the two that must be zero, and the suite
|
|
472
|
+
* asserts exactly that rather than trusting the policy table alone.
|
|
473
|
+
*/
|
|
474
|
+
export declare function auditRepresentations(rows: readonly unknown[]): RepresentationAudit;
|
|
475
|
+
/** The audit as text: what `scripts/representation-audit.mjs` prints. */
|
|
476
|
+
/**
|
|
477
|
+
* One semantic question, one primary representation — counted from real rows
|
|
478
|
+
* (B2.4).
|
|
479
|
+
*
|
|
480
|
+
* The success criterion of the ask/approval round is a *count*, so it is audited
|
|
481
|
+
* like one: an ordinary `ask_user_question` call is represented by its question
|
|
482
|
+
* card, and a generic tool row for the same call would be a second primary for one
|
|
483
|
+
* semantic event. The tool's name is not read off the row's title but off `name`,
|
|
484
|
+
* because that is the field the policy suppresses.
|
|
485
|
+
* @param rows - the transcript rows to audit.
|
|
486
|
+
* @returns the counts, with the call ids that broke the invariant.
|
|
487
|
+
*/
|
|
488
|
+
export declare function auditQuestionPrimaries(rows: readonly {
|
|
489
|
+
kind?: string;
|
|
490
|
+
callId?: string;
|
|
491
|
+
name?: string;
|
|
492
|
+
}[]): {
|
|
493
|
+
questionRows: number;
|
|
494
|
+
genericToolRows: number;
|
|
495
|
+
calls: number;
|
|
496
|
+
duplicateCalls: string[];
|
|
497
|
+
};
|
|
498
|
+
export declare function formatRepresentationAudit(audit: RepresentationAudit): string;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Screen contract: a whole-view surface with its own navigation state.
|
|
3
|
+
*
|
|
4
|
+
* A **Surface** (ask-user, a picker) borrows a few rows of the workspace and gives
|
|
5
|
+
* them back when the action is done: the transcript behind it never moves, and
|
|
6
|
+
* the reader returns to the same place. A **Screen** is the other thing entirely —
|
|
7
|
+
* it replaces the workspace, owns its own scrolling, and must be able to redraw
|
|
8
|
+
* itself from its state alone (a reattach after an SSH drop is a normal event, not
|
|
9
|
+
* an edge case). See `docs/decisions/b2-architecture-decisions.md` (AD-1 … AD-3).
|
|
10
|
+
*
|
|
11
|
+
* Depth is exactly one: a Screen may hold tabs, sections and detail modes of its
|
|
12
|
+
* own, but it never pushes another Screen. Nothing here allocates a stack, and
|
|
13
|
+
* nothing here renders — this module is the contract, `tui.ts` is the renderer.
|
|
14
|
+
*/
|
|
15
|
+
import type { Dialog, InspectDialog } from './dialogs.js';
|
|
16
|
+
import type { DiffDisplayLine } from './tool-present.js';
|
|
17
|
+
/**
|
|
18
|
+
* Which Screen is up. Adding one here is the only way to add one at all.
|
|
19
|
+
*
|
|
20
|
+
* `setup` is the odd one: it draws no body of its own — the wizard's rows are built
|
|
21
|
+
* from its state machine every frame — but it is a Screen in every other sense. It
|
|
22
|
+
* owns the whole view, it owns its input, it never enters the dialog queue, and it is
|
|
23
|
+
* rebuilt from state after a reattach (B2.6).
|
|
24
|
+
*/
|
|
25
|
+
export type ScreenKind = 'inspect' | 'report' | 'setup';
|
|
26
|
+
/** The report a `report` Screen is showing. */
|
|
27
|
+
export type ReportKind = 'status' | 'usage' | 'diag' | 'doctor' | 'help' | 'subagents';
|
|
28
|
+
/**
|
|
29
|
+
* A Screen's whole state.
|
|
30
|
+
*
|
|
31
|
+
* Everything the renderer needs is here on purpose: nothing about the picture
|
|
32
|
+
* lives only in the paint loop, which is what makes a reattach able to rebuild the
|
|
33
|
+
* same screen, and what makes the screen testable without a terminal. `offset` is
|
|
34
|
+
* the first visible body row — the Screen's own scroll position, unrelated to the
|
|
35
|
+
* transcript's `scrollOffset` behind it.
|
|
36
|
+
*/
|
|
37
|
+
export interface ScreenState {
|
|
38
|
+
kind: ScreenKind;
|
|
39
|
+
/** Title row. */
|
|
40
|
+
title: string;
|
|
41
|
+
/** Body, already rendered for the width it will be shown at. */
|
|
42
|
+
lines: readonly DiffDisplayLine[];
|
|
43
|
+
/** First visible body row. */
|
|
44
|
+
offset: number;
|
|
45
|
+
/** Which report this is, when `kind === 'report'`. */
|
|
46
|
+
report?: ReportKind;
|
|
47
|
+
/** What the copy key takes while this screen is up. */
|
|
48
|
+
copyText?: string;
|
|
49
|
+
/**
|
|
50
|
+
* A confirmation line shown in place of the navigation hint.
|
|
51
|
+
*
|
|
52
|
+
* Used by an in-screen copy ("copied 412 chars") and by a Screen's own action
|
|
53
|
+
* (the doctor repair result): the row the action would otherwise push is behind
|
|
54
|
+
* this screen, and silence after an action reads as a failure.
|
|
55
|
+
*/
|
|
56
|
+
notice?: string;
|
|
57
|
+
/** Child session whose live log refreshes this screen in place. */
|
|
58
|
+
subagentSessionId?: string;
|
|
59
|
+
/** `/find` already scrolled to its hit here; later repaints keep the offset. */
|
|
60
|
+
searchRevealed?: boolean;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The one judgement: is this dialog a Screen?
|
|
64
|
+
*
|
|
65
|
+
* A Screen is *not* a dialog — it never enters `dialogQueue`, is never replaced by
|
|
66
|
+
* a Surface and never blocks one. This function exists so that no future call site
|
|
67
|
+
* has to remember that: a screen-shaped dialog handed to the dialog path is
|
|
68
|
+
* converted here and handed to the Screen path instead, and a test pins it.
|
|
69
|
+
*
|
|
70
|
+
* Returns `undefined` for the surfaces that genuinely are dialogs (questions,
|
|
71
|
+
* confirms, the onboarding wizard).
|
|
72
|
+
*/
|
|
73
|
+
export declare function screenFromDialog(dialog: Dialog | InspectDialog): ScreenState | undefined;
|
|
74
|
+
/**
|
|
75
|
+
* The row kind a report falls back to where there is no frame to draw on.
|
|
76
|
+
*
|
|
77
|
+
* Line mode has no Screen (and no way to dismiss one), so `/diag` and `/doctor`
|
|
78
|
+
* keep their own copyable `diag` rows there — which is also where they are read
|
|
79
|
+
* from in a transcript (`/copy error` looks for `error` or `diag`).
|
|
80
|
+
*/
|
|
81
|
+
export declare function reportRowKind(report: ReportKind): 'diag' | 'system';
|
|
82
|
+
/** The rows a Screen lays out, top to bottom. */
|
|
83
|
+
export interface ScreenLayout {
|
|
84
|
+
/** 1 when the title row is drawn. */
|
|
85
|
+
titleRows: number;
|
|
86
|
+
/** 1 when the rule under the title is drawn. */
|
|
87
|
+
dividerRows: number;
|
|
88
|
+
/** Rows available to the body; may be 0 on a terminal that short. */
|
|
89
|
+
bodyRows: number;
|
|
90
|
+
/** 1 when the navigation hint row is drawn. */
|
|
91
|
+
hintRows: number;
|
|
92
|
+
/** 1 when the compact runtime strip is drawn. */
|
|
93
|
+
stripRows: number;
|
|
94
|
+
/** 0-based index of the first body row in the painted frame. */
|
|
95
|
+
contentTop: number;
|
|
96
|
+
/** 0-based index of the hint row, or -1 when it is not drawn. */
|
|
97
|
+
hintTop: number;
|
|
98
|
+
/** 0-based index of the strip row, or -1 when it is not drawn. */
|
|
99
|
+
stripTop: number;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Lay a Screen out for the height the terminal has.
|
|
103
|
+
*
|
|
104
|
+
* The strip is the one row that never goes: a Screen is a place the reader can be
|
|
105
|
+
* *while the agent keeps working*, and "is it still running, is anything waiting,
|
|
106
|
+
* is the link alive" has to be answerable without leaving. After that the order
|
|
107
|
+
* gives up the least load-bearing row first — the rule under the title, then the
|
|
108
|
+
* title, then body rows — and the navigation hint goes only when there is nothing
|
|
109
|
+
* left to navigate. Measured at 8 / 12 / 20 rows in the Screen contract tests.
|
|
110
|
+
*/
|
|
111
|
+
export declare function screenLayout(height: number): ScreenLayout;
|
|
112
|
+
/**
|
|
113
|
+
* Clamp a Screen's scroll offset to its body.
|
|
114
|
+
*
|
|
115
|
+
* The body can be shorter than the last offset the reader scrolled to (a resize, a
|
|
116
|
+
* shorter report, a live subagent log that was replaced), and an offset past the
|
|
117
|
+
* end paints an empty screen with a position readout that lies.
|
|
118
|
+
*/
|
|
119
|
+
export declare function clampScreenOffset(offset: number, lineCount: number, bodyRows: number): number;
|
|
120
|
+
/**
|
|
121
|
+
* `1–18/64`, the position readout a Screen's footer shows.
|
|
122
|
+
*
|
|
123
|
+
* Zero lines reads `0/0` rather than `1–0/0`: the second looks like a bug in the
|
|
124
|
+
* readout instead of an empty report.
|
|
125
|
+
*/
|
|
126
|
+
export declare function screenPositionText(offset: number, lineCount: number, bodyRows: number): string;
|
package/lib/types/startup.d.ts
CHANGED
|
@@ -38,6 +38,30 @@ declare module '@deepseek-ai/cordis' {
|
|
|
38
38
|
}
|
|
39
39
|
export declare const name = "ssh-tui-startup";
|
|
40
40
|
export declare const inject: string[];
|
|
41
|
+
/**
|
|
42
|
+
* Whether another application already owns this profile's command line.
|
|
43
|
+
*
|
|
44
|
+
* `@deepseek-ai/dsh-cmdline` has **no arbitration between apps**: every app plugin is
|
|
45
|
+
* handed the same argv and parses it with its own grammar, and an unknown flag is
|
|
46
|
+
* `program.error`, which exits the process. A profile that hosts two apps therefore
|
|
47
|
+
* requires the second one to understand the first one's flags — and the desktop
|
|
48
|
+
* application passes `--no-open` (the web app's own flag) to its host, so this row's
|
|
49
|
+
* grammar rejected it: the host exited 1, the application reported
|
|
50
|
+
* `dsh desktop host exited with 1: error: unknown option '--no-open'` and died. Only
|
|
51
|
+
* the *startup* row did that; the TUI row never ran, which is why 0.8.1's inert-when-
|
|
52
|
+
* headless fix could not have caught it.
|
|
53
|
+
*
|
|
54
|
+
* The signals are two, and they overlap on purpose:
|
|
55
|
+
*
|
|
56
|
+
* - the launcher facts (`desktopLauncher`): an Electron host, `DeepSeek Harness.exe`,
|
|
57
|
+
* or `app.asar` in argv — true whatever the profile's row order is;
|
|
58
|
+
* - `webStartup`: the web app provides it as soon as it has parsed, and a `web`
|
|
59
|
+
* profile is not a terminal profile in any case.
|
|
60
|
+
*
|
|
61
|
+
* Stepping aside is not a degraded mode: the TUI row `inject`s `sshTuiStartup`, so
|
|
62
|
+
* nothing of this plugin activates and the host keeps its own grammar and its own app.
|
|
63
|
+
*/
|
|
64
|
+
export declare function anotherAppOwnsCommandLine(ctx: Context): boolean;
|
|
41
65
|
/**
|
|
42
66
|
* Build the TUI command grammar and provide the session identity for the
|
|
43
67
|
* agent-loop row plus the {@link SshTuiStartup} service. On `--help` or a
|