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/tui.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ import type { Agent, ModelSelection, ModelSelectionRef } from '@deepseek-ai/dsh-
|
|
|
19
19
|
import type { Context } from '@deepseek-ai/cordis';
|
|
20
20
|
import { type LlmCallConfig } from '@deepseek-ai/dsh-llm';
|
|
21
21
|
import { SessionId, type SessionEvent } from '@deepseek-ai/dsh-session';
|
|
22
|
+
import { auditQuestionPrimaries, type RepresentationAudit } from './representation.js';
|
|
22
23
|
import type { SubagentRunEndInfo, SubagentRunInfo } from '@deepseek-ai/dsh-subagent';
|
|
23
24
|
import { type TerminalCapabilities } from './terminal-caps.js';
|
|
24
25
|
import { type SessionRoute } from './session-route.js';
|
|
@@ -29,8 +30,8 @@ import type { CollapsibleBlock, DisconnectPolicyName, Row } from './transcript-t
|
|
|
29
30
|
import { paintedLinkHits } from './term-text.js';
|
|
30
31
|
export type { CollapsibleBlock, DisplayKind, DisconnectPolicyName, PlanTodoItem, Row, SubagentLogEntry, ToolDiffHunk, } from './transcript-types.js';
|
|
31
32
|
export { clipAnsiToWidth, cursorVisualPosition, displayWidth, foldInputView, hrefAtColumn, osc52Clipboard, osc8Enabled, paintedLinkHits, fmtElapsedCompact, padAnsiToWidth, padToWidth, renderMarkdownLines, repeatToWidth, shimmerText, truncateToWidth, visibleWidth, waitCardCopy, waitSummaryFromReasoning, wrapWaitDetails, } from './term-text.js';
|
|
32
|
-
export { captureHangupSignals, composePaintOutput, detectSshSession, formatLinkQualityChip, ignoreFurtherHangupSignals, isEscapePrefix, findCursorPositionReply, isHangupErrno, linkQualityOf, linkSignalPips, paintIntervalForRtt, paintLinkLabel, parseCursorPositionReply, pickerWindowStart, probeTerminalRttMs, releaseHangupSignals, resolvePaintIntervalMs, waitUntilIdleOrTimeout, writeBootSplash, type LinkQuality, type PaintLinkKind, } from './paint.js';
|
|
33
|
-
export { CONTEXT_IDLE_COMPACT_RATIO, CONTEXT_PRESSURE_DANGER_RATIO, CONTEXT_PRESSURE_WARN_RATIO, CONTEXT_RING_EMPTY, CONTEXT_RING_SEGMENTS, contextPressureAlertText, contextPressureRingColor, contextPressureUsedTokens, contextPressureView, describeProviderRoute, dropFooterQuotaPlanName, fitFooterStatsLine, fitFooterStatusLine, footerActivity, footerIdentityParts, footerStatsGroups, footerSubagentForeign, paintFooterSubagentChip, formatContextPressureChip, formatContextPressureRing, formatContextPressureStatusLine, formatDuration, formatFooterQuota, formatQuotaBar, formatQuotaUnknown, formatStatusReport, providerHasQuotaSurface, quotaWindowTag, shortModelName, shortQuotaPlanName, formatTokens, formatTokensPerSecond, parseContextPressure, promptPressureTokens, providerShortCode, providerUsesLocalOAuth, shouldIdleAutoCompact, subagentRouteLabel, type ContextPressureSample, type ContextPressureView, type FooterActivityKind, type FooterStatsInput, type FooterStatusInput, type StatusReportInput, } from './footer.js';
|
|
33
|
+
export { captureHangupSignals, composePaintOutput, detectSshSession, formatLinkQualityChip, ignoreFurtherHangupSignals, isEscapePrefix, findCursorPositionReply, isHangupErrno, linkQualityOf, linkRedrawBudgetMs, linkSignalPips, paintIntervalForRtt, paintLinkLabel, parseCursorPositionReply, pickerWindowStart, probeTerminalRttMs, releaseHangupSignals, resolvePaintIntervalMs, waitUntilIdleOrTimeout, writeBootSplash, type LinkQuality, type PaintLinkKind, } from './paint.js';
|
|
34
|
+
export { CONTEXT_IDLE_COMPACT_RATIO, CONTEXT_PRESSURE_DANGER_RATIO, CONTEXT_PRESSURE_WARN_RATIO, CONTEXT_RING_EMPTY, CONTEXT_RING_SEGMENTS, contextPressureAlertText, contextPressureRingColor, contextPressureUsedTokens, contextPressureView, describeProviderRoute, dropFooterQuotaPlanName, fitFooterStatsLine, fitFooterStatusLine, footerActivity, footerIdentityParts, footerStatsGroups, footerSubagentForeign, paintFooterSubagentChip, formatContextPressureChip, formatContextPressureRing, formatContextPressureStatusLine, formatDuration, formatFooterQuota, formatQuotaBar, formatQuotaUnknown, formatStatusReport, formatStatusThroughput, formatStatusStats, providerHasQuotaSurface, quotaWindowTag, runtimeStrip, shortModelName, shortQuotaPlanName, formatTokens, formatTokensPerSecond, parseContextPressure, promptPressureTokens, providerShortCode, providerUsesLocalOAuth, shouldIdleAutoCompact, subagentRouteLabel, type ContextPressureSample, type ContextPressureView, type FooterActivityKind, type FooterStatsInput, type FooterStatusInput, type StatusReportInput, } from './footer.js';
|
|
34
35
|
export { commandCodePeriodStart, commandCodeSourceFor, crossedQuotaThresholds, formatAccountBalance, formatFooterBalance, formatOpenCodeGoUsage, formatQuotaSnapshot, formatQuotaStatusLine, joinUrl, openCodeSourceFor, parseCommandCodeQuota, parseDeepSeekBalance, parseOpenAiCompatibleBalance, parseOpenCodeGoQuota, parseSuperGrokBilling, preferredQuotaWindow, quotaAlertText, quotaRefreshEverySteps, quotaRefreshEveryTurns, remainingPercentFromUsed, tightestQuotaWindow, type AccountBalanceLine, type AccountBalanceSnapshot, type CommandCodeSource, type OpenCodeFlavor, type OpenCodeSource, type QuotaPeriod, type QuotaSnapshot, type QuotaWindow, } from './quota.js';
|
|
35
36
|
export { commandAcceptsAttachments, forEachSessionEvent, isTokenDeltaChunk, listPersistenceHeaders, inspectPersistenceSession, sessionEvents, settingsNamespace, streamChunkOf, streamFirstTokenTime, streamFrameAttemptId, streamFrameOwner, } from './dsh-compat.js';
|
|
36
37
|
export { applyTurnEndToPlan, askSummary, cardCategoryOf, compactionHeaderText, formatCompactCommandError, isPromptInjectionMessage, matchTranscriptRows, parseFindQuery, parsePlanTodos, planCloseNudgeText, planDockNote, planIsLive, planTitleFromMarkdown, planTurnLeftOpen, promptInjectionSources, promptInjectionTitle, buildSubagentHeader, clipSubagentActivity, describeSubagentFailure, subagentChipSummary, subagentDisplayName, subagentInspectLines, subagentRowFromSpawnTool, foldSubagentUserLog, todoProgressLabel, todoSummary, type CardCategory, } from './plan.js';
|
|
@@ -137,6 +138,14 @@ export interface TuiController {
|
|
|
137
138
|
*/
|
|
138
139
|
sessionHadUserInput(): boolean;
|
|
139
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* `DSH_TUI_FEEDBACK_MS` — how long that is, in milliseconds.
|
|
143
|
+
*
|
|
144
|
+
* `0` is the pre-0.8.2 behaviour and a legitimate preference: keep an acknowledgement
|
|
145
|
+
* or a warning until the reader submits something. Anything unreadable or negative
|
|
146
|
+
* falls back to the default rather than to a surprise.
|
|
147
|
+
*/
|
|
148
|
+
export declare function resolveFeedbackTtlMs(env?: NodeJS.ProcessEnv): number;
|
|
140
149
|
export type WorkspaceView = 'detailed' | 'compact';
|
|
141
150
|
/**
|
|
142
151
|
* One row the card cursor can land on.
|
|
@@ -227,12 +236,130 @@ export declare class SshTui {
|
|
|
227
236
|
private historyDraft;
|
|
228
237
|
private status;
|
|
229
238
|
private dialog;
|
|
239
|
+
/**
|
|
240
|
+
* What the open dialog *means*, which its shape cannot say: `/model` and
|
|
241
|
+
* `ask_user_question` are both `questions` dialogs, and only one of them means
|
|
242
|
+
* the agent is waiting for a person. Declared by whoever opens the dialog.
|
|
243
|
+
*/
|
|
244
|
+
private dialogRole;
|
|
230
245
|
private readonly dialogQueue;
|
|
231
246
|
private onboardingCompletion;
|
|
232
247
|
private dirty;
|
|
233
248
|
private disposed;
|
|
234
249
|
private exiting;
|
|
235
250
|
private hangingUp;
|
|
251
|
+
/** How long the last frame took to compose, for the resize pacing below. */
|
|
252
|
+
private lastPaintCostMs;
|
|
253
|
+
private resizeLastEventAt;
|
|
254
|
+
private resizeLastPaintAt;
|
|
255
|
+
private resizePaintTimer;
|
|
256
|
+
/**
|
|
257
|
+
* Whether a resize frame is currently rendering only the transcript's tail
|
|
258
|
+
* (see {@link narrowToResizeTail}), so the drag's last geometry can be painted
|
|
259
|
+
* whole.
|
|
260
|
+
*/
|
|
261
|
+
private resizeTailNarrowed;
|
|
262
|
+
/**
|
|
263
|
+
* A resize arrived: paint the size the window is *at*, not every size it
|
|
264
|
+
* passed through.
|
|
265
|
+
*
|
|
266
|
+
* Dragging an edge emits an event every few milliseconds, and each one used to
|
|
267
|
+
* get a synchronous full repaint — on a long transcript that is ~100 ms of
|
|
268
|
+
* rendering per event, so a drag queued seconds of work and the screen trailed
|
|
269
|
+
* the pointer. Nothing about an intermediate geometry is worth a frame: the
|
|
270
|
+
* reader cannot use it and the next event invalidates it. Painting the *first*
|
|
271
|
+
* event of a burst is no better, because that geometry is stale by the time the
|
|
272
|
+
* frame is composed and it costs the frame that is actually wanted.
|
|
273
|
+
*
|
|
274
|
+
* So the frame has a deadline, recomputed on every event:
|
|
275
|
+
*
|
|
276
|
+
* - {@link RESIZE_SETTLE_MS} after the newest event (the size has stopped
|
|
277
|
+
* moving, so this is the geometry the user is looking at), and
|
|
278
|
+
* - at the latest one {@link resizeBudgetMs} after the last frame, so a drag
|
|
279
|
+
* that never pauses still shows the chrome following the pointer.
|
|
280
|
+
*
|
|
281
|
+
* Whichever comes first wins, and the frame is composed from the terminal's
|
|
282
|
+
* size at the moment it runs — so a newer geometry can never be painted after
|
|
283
|
+
* an older one; the older one is simply never painted.
|
|
284
|
+
*/
|
|
285
|
+
private requestResizePaint;
|
|
286
|
+
/**
|
|
287
|
+
* Whether the previous *drag* frame is still on its way to the terminal.
|
|
288
|
+
*
|
|
289
|
+
* A drag frame is not a token batch: the reader is moving the edge and watching
|
|
290
|
+
* for the screen to agree with it, so a frame composed now would be superseded
|
|
291
|
+
* before it landed. What matters is the *queue*, not the pipe's absolute
|
|
292
|
+
* capacity — measured on a byte-rate-limited consumer, a drag toward a wider
|
|
293
|
+
* window is the case that hurts, because a frame's size is proportional to the
|
|
294
|
+
* width: every frame is bigger than the last while the terminal drains at a
|
|
295
|
+
* fixed rate, the backlog compounds, and the screen keeps moving for tens of
|
|
296
|
+
* milliseconds after the pointer has stopped (30–44 ms measured, against 0–10 ms
|
|
297
|
+
* for the shrinking direction).
|
|
298
|
+
*
|
|
299
|
+
* So the threshold is one frame, not a fixed 32 KB. That constant is right for
|
|
300
|
+
* the cadence path, where the question is "is this link slow"; here the question
|
|
301
|
+
* is "would this frame be a stale geometry queued behind a live one", and the
|
|
302
|
+
* answer is yes as soon as the last one has not been accepted — however small
|
|
303
|
+
* both frames are. The named constant stays as the floor, so a long session's
|
|
304
|
+
* frames can queue a little rather than starving the drag of feedback.
|
|
305
|
+
*/
|
|
306
|
+
private resizeWireBehind;
|
|
307
|
+
/** Bytes the last frame carried, whoever owns the wire (see above). */
|
|
308
|
+
private lastFrameBytes;
|
|
309
|
+
private pendingBytes;
|
|
310
|
+
/**
|
|
311
|
+
* Cap what a resize frame re-renders, and remember that the frame is a
|
|
312
|
+
* compromise so the drag's last geometry can be painted whole.
|
|
313
|
+
*
|
|
314
|
+
* Both matters, and they are different matters: the cap is what keeps the
|
|
315
|
+
* event loop free while the pointer moves (see
|
|
316
|
+
* {@link RESIZE_TAIL_MIN_ROWS}), and the remembered flag is what stops the
|
|
317
|
+
* compromise from *becoming* the screen — the moment the drag pauses, the
|
|
318
|
+
* transcript is painted in full again at the settled width.
|
|
319
|
+
*
|
|
320
|
+
* A scroll position is left alone: a reader who has scrolled back is looking
|
|
321
|
+
* at rows the tail does not contain, and moving the view under them would be a
|
|
322
|
+
* bigger lie than a slower frame. `paintTailBudget` already refuses to fold
|
|
323
|
+
* anything while `scrollOffset > 0` for the same reason.
|
|
324
|
+
*/
|
|
325
|
+
private narrowToResizeTail;
|
|
326
|
+
/**
|
|
327
|
+
* Undo {@link narrowToResizeTail}: the drag has settled, so the frame that is
|
|
328
|
+
* about to be painted must be the whole transcript at the final width.
|
|
329
|
+
*/
|
|
330
|
+
private widenPastResizeTail;
|
|
331
|
+
/**
|
|
332
|
+
* The longest a resize frame may wait when the wire cannot take another one yet.
|
|
333
|
+
*
|
|
334
|
+
* This is a *ceiling*, not a rate: the normal case paints at once (see
|
|
335
|
+
* {@link requestResizePaint}), and this only decides how often a link that is
|
|
336
|
+
* genuinely behind is asked again — the previous frame has not drained, so the
|
|
337
|
+
* next one would be a stale geometry queued behind a live one. Two costs feed it:
|
|
338
|
+
*
|
|
339
|
+
* - **composing** a frame — measured (`lastPaintCostMs`): a short transcript is a
|
|
340
|
+
* few milliseconds and a long one is a hundred, and repainting faster than that
|
|
341
|
+
* starves the event loop that has to receive the next resize;
|
|
342
|
+
* - **delivering** one on a link whose speed is unknown (see
|
|
343
|
+
* {@link linkRedrawBudgetMs}: only an unprobed link falls back to a slow
|
|
344
|
+
* budget, because there the interval in force is a number nobody measured).
|
|
345
|
+
*
|
|
346
|
+
* A probed link does not need the second term: the backlog check above already
|
|
347
|
+
* says whether it is keeping up, and pacing it from the round trip as well would
|
|
348
|
+
* put the artificial delay back that this function exists to avoid.
|
|
349
|
+
*/
|
|
350
|
+
private resizeBudgetMs;
|
|
351
|
+
/**
|
|
352
|
+
* Whether a resize is holding the frame.
|
|
353
|
+
*
|
|
354
|
+
* While a deadline is armed the burst is collapsing, and the cadence timer must
|
|
355
|
+
* not paint on its own: it fires on the paint interval, which is *shorter* than
|
|
356
|
+
* the budget a heavy session needs, so leaving both flushers live put the frame
|
|
357
|
+
* count back near one per event — the cost this exists to remove. Content that
|
|
358
|
+
* changes mid-drag waits at most one budget.
|
|
359
|
+
*/
|
|
360
|
+
private resizeBurstOpen;
|
|
361
|
+
/** (Re)arm the frame's deadline. Every event pushes it out. */
|
|
362
|
+
private armResizePaint;
|
|
236
363
|
private readonly onDirectResize;
|
|
237
364
|
private readonly headlessDisplay;
|
|
238
365
|
private disconnectPolicy;
|
|
@@ -377,9 +504,33 @@ export declare class SshTui {
|
|
|
377
504
|
private liveStreamOwner;
|
|
378
505
|
/** Live events parked while the (yielding) history replay holds the floor. */
|
|
379
506
|
private replayQueue;
|
|
507
|
+
/**
|
|
508
|
+
* True while the durable log is still being read into the transcript.
|
|
509
|
+
*
|
|
510
|
+
* Frames are not composed during it (see `paint`): a window anchored to a session
|
|
511
|
+
* that is still growing is the rolling resume this flag exists to prevent.
|
|
512
|
+
*/
|
|
513
|
+
private loadingHistory;
|
|
514
|
+
/**
|
|
515
|
+
* A relay attached while the log was still being read, so the screen entry it
|
|
516
|
+
* asks for has not been written yet (see `attachRelayDisplay` and `paint`).
|
|
517
|
+
*/
|
|
518
|
+
private screenEntryPending;
|
|
380
519
|
/** A relay claimed the display while a hangup was still cancelling/flushing. */
|
|
381
520
|
private reattachedDuringHangup;
|
|
382
521
|
private scrollOffset;
|
|
522
|
+
/**
|
|
523
|
+
* How many rows `/clear` hid, as a count from the front of `rows`.
|
|
524
|
+
*
|
|
525
|
+
* A *view* boundary, not a deletion (AD-4): the rows stay, the session log is
|
|
526
|
+
* untouched, and a resume shows everything again because this number is Host-local
|
|
527
|
+
* and never written anywhere. A count is the stable expression here — every new row
|
|
528
|
+
* is appended at the end, and an update to an old row mutates it in place, so a row
|
|
529
|
+
* cannot cross the boundary by being updated. When the transcript trims its oldest
|
|
530
|
+
* rows the count is decremented with them, which keeps the boundary on the same
|
|
531
|
+
* semantic row.
|
|
532
|
+
*/
|
|
533
|
+
private clearedRows;
|
|
383
534
|
private readonly clickableRows;
|
|
384
535
|
/**
|
|
385
536
|
* The transcript as painted, for free-form selection. Only the lines that came
|
|
@@ -389,6 +540,86 @@ export declare class SshTui {
|
|
|
389
540
|
private selectableLines;
|
|
390
541
|
/** Screen row (1-based) of the first transcript line in the last frame. */
|
|
391
542
|
private transcriptTopScreenY;
|
|
543
|
+
/**
|
|
544
|
+
* The footer echo: the last thing this Host confirmed to the reader.
|
|
545
|
+
*
|
|
546
|
+
* Host-local and deliberately not history (AD-13): "theme switched" is an
|
|
547
|
+
* acknowledgement, not a fact about the session. It lives in the footer's own row
|
|
548
|
+
* as the lowest-priority chip, is replaced by the next echo, and leaves the row
|
|
549
|
+
* again when the reader submits — or after `feedbackTtlMs`, whichever comes first.
|
|
550
|
+
* A detach keeps it (the Host never died); a resume does not restore it (it was
|
|
551
|
+
* never in the log).
|
|
552
|
+
*/
|
|
553
|
+
private footerEcho;
|
|
554
|
+
/**
|
|
555
|
+
* The ephemeral notice: feedback that needs reading now, and is not history.
|
|
556
|
+
*
|
|
557
|
+
* A failed command, an operational warning — messages with too much in them for a
|
|
558
|
+
* footer chip. It takes the telemetry row for its lifetime: no geometry change, no
|
|
559
|
+
* content hidden, always visible. It does not steal input, and the next notice
|
|
560
|
+
* replaces it while the next submit (or its own expiry) clears it. No NotificationManager: one field,
|
|
561
|
+
* one row.
|
|
562
|
+
*/
|
|
563
|
+
private notice;
|
|
564
|
+
/**
|
|
565
|
+
* The Screen that is up, if any. At most one, and never a dialog.
|
|
566
|
+
*
|
|
567
|
+
* A Screen replaces the workspace rather than borrowing rows from it, so it is
|
|
568
|
+
* deliberately *not* part of `dialog`/`dialogQueue`: a queued question cannot
|
|
569
|
+
* open behind a report, and a report cannot swallow a question. Nothing else in
|
|
570
|
+
* this class needs to know which Screen it is — `paintScreen` renders whatever
|
|
571
|
+
* state is here, and every key gate reads this one field (B2.1).
|
|
572
|
+
*/
|
|
573
|
+
private screen;
|
|
574
|
+
/**
|
|
575
|
+
* A Screen's own action Surface (the doctor confirmation).
|
|
576
|
+
*
|
|
577
|
+
* It exists so the channel stays separated in both directions: while a Screen is
|
|
578
|
+
* up, a confirmation it needs must not enter the workspace queue, and a
|
|
579
|
+
* workspace Surface that arrives meanwhile must not be consumed by the Screen
|
|
580
|
+
* (AD-3). At most one, resolved by the Screen that opened it.
|
|
581
|
+
*/
|
|
582
|
+
private screenSurface;
|
|
583
|
+
/** Resolver for `screenSurface`, so closing it always settles its promise. */
|
|
584
|
+
private screenSurfaceResolve;
|
|
585
|
+
/**
|
|
586
|
+
* Whether the next Screen frame must establish the whole picture.
|
|
587
|
+
*
|
|
588
|
+
* True when a Screen opens and after a reattach: the terminal is empty or holds
|
|
589
|
+
* an unrelated frame, and one full paint is both cheaper and less surprising than
|
|
590
|
+
* a partial one against unknown contents. Every later frame is incremental.
|
|
591
|
+
*/
|
|
592
|
+
private screenNeedsFullPaint;
|
|
593
|
+
/** The strip's last painted text, so an unchanged strip is not re-sent. */
|
|
594
|
+
private lastScreenStrip;
|
|
595
|
+
/**
|
|
596
|
+
* The rows the transient layer covers, 1-based and inclusive of `top`..`top+rows-1`.
|
|
597
|
+
*
|
|
598
|
+
* The layer is drawn over the transcript, so anything the frame registers as
|
|
599
|
+
* clickable or selectable below this line is a target the reader cannot see.
|
|
600
|
+
* The mouse handlers already refuse to start while a surface owns the keyboard;
|
|
601
|
+
* this makes the maps themselves honest, so a later change cannot introduce
|
|
602
|
+
* click-through by forgetting that gate. B1.2 widened what the layer carries —
|
|
603
|
+
* a question and a control-plane picker both land here — but not what it means:
|
|
604
|
+
* covered rows are hidden, never re-defined, and the composer's boundary is
|
|
605
|
+
* always below the last of them.
|
|
606
|
+
*/
|
|
607
|
+
private interactionRegion;
|
|
608
|
+
/**
|
|
609
|
+
* The rows the live tail covers, 1-based, same convention as the layer above.
|
|
610
|
+
*
|
|
611
|
+
* The tail is a *projection* of runtime state (streaming text, the wait card),
|
|
612
|
+
* not history: it is drawn over the bottom of the window and owns no source row.
|
|
613
|
+
* Rows under it are hidden, so — exactly like the interaction layer — nothing
|
|
614
|
+
* behind them may stay a click, drop or link target.
|
|
615
|
+
*/
|
|
616
|
+
private liveTailRegion;
|
|
617
|
+
/**
|
|
618
|
+
* The interaction row that must stay visible when the layer has to window
|
|
619
|
+
* itself (the highlighted option): on a short terminal the list is scrolled to
|
|
620
|
+
* the selection instead of being clipped at the bottom.
|
|
621
|
+
*/
|
|
622
|
+
private dialogFocusLine;
|
|
392
623
|
/** Where a drag started; the run it belongs to decides what can be selected. */
|
|
393
624
|
private mouseAnchor;
|
|
394
625
|
/**
|
|
@@ -408,8 +639,32 @@ export declare class SshTui {
|
|
|
408
639
|
/** Screen-row → OSC 8 hits from the last paint (tests). */
|
|
409
640
|
get linkHitsByRow(): Map<number, ReturnType<typeof paintedLinkHits>>;
|
|
410
641
|
private streamingReasoning;
|
|
642
|
+
/**
|
|
643
|
+
* The reader's expansion choice for the live thinking card, for this turn.
|
|
644
|
+
*
|
|
645
|
+
* A turn thinks in phases: think, call a tool, think again. Each phase used to
|
|
646
|
+
* start a fresh collapsed card, so a reader who had just expanded the card being
|
|
647
|
+
* written watched it fold itself shut at the next phase — "the thinking card
|
|
648
|
+
* cannot be expanded" as the reader experiences it, even though each individual
|
|
649
|
+
* card could be. The choice is the reader's, it belongs to the turn, and
|
|
650
|
+
* `turn/start` is the only thing that resets it.
|
|
651
|
+
*/
|
|
652
|
+
private reasoningExpandedChoice;
|
|
411
653
|
private escapeBuffer;
|
|
412
654
|
private escapeTimer;
|
|
655
|
+
/**
|
|
656
|
+
* How long an acknowledgement or a warning stays on the chrome.
|
|
657
|
+
*
|
|
658
|
+
* B2.3b kept them until the next submit, with no timer, so a reader who came back
|
|
659
|
+
* after a while still saw what happened. Measured against the actual use — a
|
|
660
|
+
* "copied 412 characters" chip and a "applies at the next step" notice — that is
|
|
661
|
+
* the wrong trade: the reader has read them within seconds, and until they type
|
|
662
|
+
* something the row is occupied by a sentence about the past instead of the
|
|
663
|
+
* telemetry it replaced. They now expire on their own (and a submit still clears
|
|
664
|
+
* them at once).
|
|
665
|
+
*/
|
|
666
|
+
private feedbackTtlMs;
|
|
667
|
+
private feedbackTimer;
|
|
413
668
|
/**
|
|
414
669
|
* Cursor-position replies removed from the relay's stdin stream. A launcher
|
|
415
670
|
* from an older release (or a reply that raced its own probe) would otherwise
|
|
@@ -457,7 +712,13 @@ export declare class SshTui {
|
|
|
457
712
|
* mean touching the filesystem on every repaint.
|
|
458
713
|
*/
|
|
459
714
|
private rosterMissing;
|
|
460
|
-
/**
|
|
715
|
+
/**
|
|
716
|
+
* Screen row of the install warning, for click-to-doctor.
|
|
717
|
+
*
|
|
718
|
+
* It is a chip on the status row like any other — that row is the last line of
|
|
719
|
+
* the frame, so its index is the frame's last row — and clicking anywhere on it
|
|
720
|
+
* opens the report that explains it.
|
|
721
|
+
*/
|
|
461
722
|
private healthChipRow;
|
|
462
723
|
/** How much colour this terminal can take (see `color-depth`). */
|
|
463
724
|
private readonly colorDepth;
|
|
@@ -479,6 +740,44 @@ export declare class SshTui {
|
|
|
479
740
|
/** Approvals refused because nobody could confirm them (a subset of denials). */
|
|
480
741
|
private detachedDeniedCount;
|
|
481
742
|
/** Questions waiting for a display right now — their cards do not exist yet. */
|
|
743
|
+
/**
|
|
744
|
+
* The questions each `ask_user_question` call recorded, and the cards drawn for
|
|
745
|
+
* them.
|
|
746
|
+
*
|
|
747
|
+
* Keyed by call *and* question id: one call carries a batch, and two calls may
|
|
748
|
+
* reuse a question id. The card is the presentation only — the Session's
|
|
749
|
+
* projection is what says whether a question is still answerable and what it was
|
|
750
|
+
* answered with, so a replayed session draws the same card a lived one did.
|
|
751
|
+
*/
|
|
752
|
+
private readonly askedByCall;
|
|
753
|
+
private readonly questionCards;
|
|
754
|
+
/**
|
|
755
|
+
* Every plan-relevant durable event of this session, in order (B2.5).
|
|
756
|
+
*
|
|
757
|
+
* The plan artifact is a *projection* of these, so nothing here is plan state: the
|
|
758
|
+
* fold decides, and the transcript row, the dock and the review Surface all read
|
|
759
|
+
* the result. `live` marks the events that arrived from the running Host, because
|
|
760
|
+
* only they can carry an unanswered review (a resumed process never finishes one).
|
|
761
|
+
*/
|
|
762
|
+
private planEvents;
|
|
763
|
+
/** The folded artifacts. Recomputed whenever `planEvents` grows. */
|
|
764
|
+
private planArtifacts;
|
|
765
|
+
/** The state last *said* in the transcript per artifact, so a transition is one row. */
|
|
766
|
+
private readonly planAnnounced;
|
|
767
|
+
/**
|
|
768
|
+
* The approvals the Session log has recorded, by the Harness's own `id`.
|
|
769
|
+
*
|
|
770
|
+
* `approval/asked` states the request (and the tool call it guards),
|
|
771
|
+
* `approval/decided` states the outcome, and the two are joined here so a
|
|
772
|
+
* decision that arrives before its row exists — or a row that arrives after both
|
|
773
|
+
* — still lands on the same card. Nothing is written back: the log is the
|
|
774
|
+
* Harness's, and this plugin keeps no approval history of its own (B2.4).
|
|
775
|
+
*/
|
|
776
|
+
private readonly approvalAsked;
|
|
777
|
+
/** Approvals resolved before their tool card existed, waiting for it. */
|
|
778
|
+
private readonly pendingApprovalByCall;
|
|
779
|
+
/** Cards that exist only because a live request opened them (no durable call id). */
|
|
780
|
+
private liveQuestionSeq;
|
|
482
781
|
private queuedQuestions;
|
|
483
782
|
/** When the current detached wait began, so the reconnect can say how long. */
|
|
484
783
|
private questionWaitSince;
|
|
@@ -493,6 +792,13 @@ export declare class SshTui {
|
|
|
493
792
|
*/
|
|
494
793
|
private sshSession;
|
|
495
794
|
private paintProbed;
|
|
795
|
+
/**
|
|
796
|
+
* Where {@link paintIntervalMs} came from: a measured round trip, an explicit
|
|
797
|
+
* `DSH_TUI_PAINT_MS`, or a local TTY's constant. Only the *unprobed SSH* case is
|
|
798
|
+
* an unknown link, and only that case falls back to
|
|
799
|
+
* {@link RESIZE_UNKNOWN_LINK_BUDGET_MS}.
|
|
800
|
+
*/
|
|
801
|
+
private paintCadence;
|
|
496
802
|
private paintRttMs;
|
|
497
803
|
/** The last few measured round-trips; the chip and the budget use their median. */
|
|
498
804
|
private paintRttHistory;
|
|
@@ -631,6 +937,16 @@ export declare class SshTui {
|
|
|
631
937
|
* user wrote is never touched, and the previous file is kept as a backup.
|
|
632
938
|
*/
|
|
633
939
|
private repairProfilePatch;
|
|
940
|
+
/**
|
|
941
|
+
* Where an action's outcome is reported: the Screen that asked, or a row.
|
|
942
|
+
*
|
|
943
|
+
* A Screen's own action is answered *on the Screen* — the reader is looking at it,
|
|
944
|
+
* and the row it would otherwise push lands behind their own view. With no Screen
|
|
945
|
+
* up (the same code path serves `/fix`), the answer is an ordinary row.
|
|
946
|
+
*/
|
|
947
|
+
private resultChannel;
|
|
948
|
+
/** Report a Screen action's outcome, or fall back to a row. */
|
|
949
|
+
private reportActionResult;
|
|
634
950
|
/** Human-facing exit with goodbye and flush; called from key handling. */
|
|
635
951
|
requestExit(code: number): Promise<void>;
|
|
636
952
|
/**
|
|
@@ -691,9 +1007,57 @@ export declare class SshTui {
|
|
|
691
1007
|
private toolCardSummary;
|
|
692
1008
|
private mergeIntoToolCard;
|
|
693
1009
|
private findToolRowByCallId;
|
|
1010
|
+
/**
|
|
1011
|
+
* The approval state of one tool call (B2.4).
|
|
1012
|
+
*
|
|
1013
|
+
* Approval has no row of its own: it is a field of the card for the call it
|
|
1014
|
+
* guarded. A decision can arrive before the card does (a `tool/result`-only
|
|
1015
|
+
* fragment, a resumed log read out of order), so an unresolved call id is kept
|
|
1016
|
+
* until its row appears rather than dropped.
|
|
1017
|
+
* @param callId - the tool call the approval belongs to.
|
|
1018
|
+
* @param next - the fact being applied, or undefined to read it back.
|
|
1019
|
+
* @returns the field after the merge.
|
|
1020
|
+
*/
|
|
1021
|
+
private setToolApproval;
|
|
1022
|
+
/**
|
|
1023
|
+
* The weakest approval reading there is, and the only one allowed to be weak.
|
|
1024
|
+
*
|
|
1025
|
+
* A log from before the Harness recorded its audit pair still says *something*
|
|
1026
|
+
* about a refused tool: the tool never ran, and the reason is the Harness's own
|
|
1027
|
+
* sentence for an approval that came back rejected or cancelled. That is a
|
|
1028
|
+
* reading, not a decision, so it is filed as `inferred` — and it can only ever
|
|
1029
|
+
* produce `rejected`/`unknown`, never `approved`: a tool that ran proves nothing,
|
|
1030
|
+
* because a tool nobody had to approve runs too.
|
|
1031
|
+
*
|
|
1032
|
+
* Anything already known from a stronger source wins, which is also what keeps a
|
|
1033
|
+
* live decision from being rewritten by the sentence it produced.
|
|
1034
|
+
* @param row - the card that just settled.
|
|
1035
|
+
* @param message - the tool result's message.
|
|
1036
|
+
* @param error - the result's error, when it has one.
|
|
1037
|
+
*/
|
|
1038
|
+
private inferApprovalFromResult;
|
|
1039
|
+
/** Hand a card the approval that was resolved before the card existed. */
|
|
1040
|
+
private adoptPendingApproval;
|
|
694
1041
|
private findMergeableToolRow;
|
|
695
1042
|
/** Append one transcript row, bounding memory on long sessions. */
|
|
1043
|
+
/**
|
|
1044
|
+
* The only way a line enters the transcript.
|
|
1045
|
+
*
|
|
1046
|
+
* It takes a `Representation` — a row *plus* the policy that produced it — and not
|
|
1047
|
+
* a `Row`, so a creation site cannot skip the classification: a bare row is a type
|
|
1048
|
+
* error, and a source id with no policy is one too (`RepresentationSource` is the
|
|
1049
|
+
* policy table's keys). The policy is copied onto the row here, which is what makes
|
|
1050
|
+
* a real transcript auditable afterwards: `auditRepresentations(this.rows)` can
|
|
1051
|
+
* then answer how many lines are history and how many are the Host's own voice.
|
|
1052
|
+
*
|
|
1053
|
+
* A JavaScript caller (a test fixture, an embedder) can still hand over something
|
|
1054
|
+
* that is not a representation. That case is *recorded*, not defaulted: the row is
|
|
1055
|
+
* pushed with no metadata and `unclassifiedRepresentations` counts it, so the audit
|
|
1056
|
+
* fails loudly instead of quietly calling an unknown line durable.
|
|
1057
|
+
*/
|
|
696
1058
|
private pushRow;
|
|
1059
|
+
/** Append one classified row to the transcript (the `transcript` destination). */
|
|
1060
|
+
private appendRow;
|
|
697
1061
|
/** Append one event, holding it until a display can carry it. */
|
|
698
1062
|
private writeLineMode;
|
|
699
1063
|
/** Hand the held lines to the display that just attached. */
|
|
@@ -713,6 +1077,13 @@ export declare class SshTui {
|
|
|
713
1077
|
* painted, so anything not in the rows is appended.
|
|
714
1078
|
*/
|
|
715
1079
|
private focusRing;
|
|
1080
|
+
/**
|
|
1081
|
+
* The live thinking block, created on first use.
|
|
1082
|
+
*
|
|
1083
|
+
* Expanded or not is the reader's choice for the turn, not this frame's: see
|
|
1084
|
+
* `reasoningExpandedChoice`.
|
|
1085
|
+
*/
|
|
1086
|
+
private liveThinkingBlock;
|
|
716
1087
|
private spinnerFrame;
|
|
717
1088
|
/**
|
|
718
1089
|
* Codex wait card: shown while the turn is running. The live thinking
|
|
@@ -729,14 +1100,66 @@ export declare class SshTui {
|
|
|
729
1100
|
/** Courtesy name for a child session; never the raw session hash. */
|
|
730
1101
|
private subagentNameFor;
|
|
731
1102
|
private findLivePlanRow;
|
|
1103
|
+
/**
|
|
1104
|
+
* Keep the plan log, and re-fold when it grew.
|
|
1105
|
+
*
|
|
1106
|
+
* Only the events the fold reads are kept (B2.5). They are rare — a plan mode
|
|
1107
|
+
* switch, a todo snapshot, a review — so a whole re-fold per plan event stays
|
|
1108
|
+
* trivial, and it keeps the projection a *pure function of the log* rather than a
|
|
1109
|
+
* running tally that could drift from one.
|
|
1110
|
+
*/
|
|
1111
|
+
private recordPlanEvent;
|
|
1112
|
+
/**
|
|
1113
|
+
* The projection, onto the rows three readers share.
|
|
1114
|
+
*
|
|
1115
|
+
* One artifact, one row: the row is the artifact's *reference* in the transcript
|
|
1116
|
+
* (its lifecycle summary and, when expanded, its steps), the dock paints the live
|
|
1117
|
+
* one, and the review Surface is the dialog. None of the three keeps plan state of
|
|
1118
|
+
* its own (AD-15): they all read what this fold decided.
|
|
1119
|
+
*/
|
|
1120
|
+
private syncPlanArtifacts;
|
|
1121
|
+
/** The row that stands for one artifact, if it has been drawn yet. */
|
|
1122
|
+
private planRowFor;
|
|
1123
|
+
/** The artifact a docked row belongs to, when the workspace should show one. */
|
|
1124
|
+
private dockedPlanRow;
|
|
1125
|
+
private createPlanRow;
|
|
1126
|
+
/**
|
|
1127
|
+
* Say one lifecycle transition, once.
|
|
1128
|
+
*
|
|
1129
|
+
* The transcript keeps the artifact's *lifecycle*: entering plan mode, a review's
|
|
1130
|
+
* answer, leaving the mode. It does not keep a second copy of the plan body — that
|
|
1131
|
+
* is the artifact's (the dock while it is live, the review Surface while it is under
|
|
1132
|
+
* review, this row's overlay afterwards) — and a transition is never announced
|
|
1133
|
+
* twice, which is what makes the fold idempotent under a re-`sync`.
|
|
1134
|
+
*/
|
|
1135
|
+
private announcePlanTransition;
|
|
732
1136
|
/** Older / finished plans stay in the scrolling transcript. */
|
|
733
1137
|
private archiveStalePlans;
|
|
734
|
-
|
|
1138
|
+
/**
|
|
1139
|
+
* There used to be a second plan-row writer here (`upsertPlanRow`).
|
|
1140
|
+
*
|
|
1141
|
+
* B2.5 moved every plan row onto the artifact fold — one artifact, one row, and
|
|
1142
|
+
* `syncPlanArtifacts` is the only place a plan row is created — which left this
|
|
1143
|
+
* one with no caller. The B2 final audit removed it rather than leave a second
|
|
1144
|
+
* truth in the file for someone to find and call.
|
|
1145
|
+
*/
|
|
735
1146
|
/** Whether the live plan strip should occupy the workspace footer. */
|
|
736
1147
|
private shouldDockPlan;
|
|
737
1148
|
/** Send the leftover-todo nudge only from true idle, so /compact is not blocked. */
|
|
738
1149
|
private flushPlanCloseNudge;
|
|
739
1150
|
/** Compact web-style plan strip pinned above the input, not in the transcript. */
|
|
1151
|
+
/**
|
|
1152
|
+
* The live region's runtime lines: the thinking card, the streamed text, the
|
|
1153
|
+
* wait card — in that order.
|
|
1154
|
+
*
|
|
1155
|
+
* Built before the window is computed, because the region *reserves* rows now:
|
|
1156
|
+
* `paint` needs its height before it can size the transcript. `waitStart` is
|
|
1157
|
+
* where the wait card begins, which is where the compact view splices the
|
|
1158
|
+
* running burst (that is where the transcript paints it for a settled reply).
|
|
1159
|
+
* `thinkingRows` is how many lines the live thinking card owns, so a click on
|
|
1160
|
+
* them reaches the card.
|
|
1161
|
+
*/
|
|
1162
|
+
private runtimeTailLines;
|
|
740
1163
|
private paintPlanDock;
|
|
741
1164
|
/**
|
|
742
1165
|
* One diff line with its changed characters in reverse video.
|
|
@@ -758,8 +1181,133 @@ export declare class SshTui {
|
|
|
758
1181
|
*/
|
|
759
1182
|
private styleColumnedLine;
|
|
760
1183
|
private paintToolBodyLine;
|
|
1184
|
+
/**
|
|
1185
|
+
* The representations this transcript is made of (B2.3a's audit artifact).
|
|
1186
|
+
*
|
|
1187
|
+
* Reads the policy off the rows themselves, so it reports what is really in the
|
|
1188
|
+
* transcript rather than what the table says should be — including anything that
|
|
1189
|
+
* arrived unclassified.
|
|
1190
|
+
*/
|
|
1191
|
+
representationAudit(): RepresentationAudit;
|
|
1192
|
+
/**
|
|
1193
|
+
* The ask-user half of the audit: one semantic question, one primary row.
|
|
1194
|
+
*
|
|
1195
|
+
* Read off the real transcript rather than declared, because the invariant is
|
|
1196
|
+
* about what a reader can see: a call with both a question card and a generic
|
|
1197
|
+
* tool card is two primaries for one event, and the count is what catches a
|
|
1198
|
+
* regression (B2.4).
|
|
1199
|
+
*/
|
|
1200
|
+
questionPrimaryAudit(): ReturnType<typeof auditQuestionPrimaries>;
|
|
1201
|
+
/**
|
|
1202
|
+
* Drop an acknowledgement or a warning whose time is up.
|
|
1203
|
+
*
|
|
1204
|
+
* Called before a frame is built and by the expiry timer, so every reader — the
|
|
1205
|
+
* footer, the Screen strip, `/diag` and the tests — sees the same thing at the same
|
|
1206
|
+
* moment: the row is the session's again once the sentence has been readable.
|
|
1207
|
+
*/
|
|
1208
|
+
private expireFeedback;
|
|
1209
|
+
/**
|
|
1210
|
+
* Repaint when the newest feedback expires, so the row goes back by itself.
|
|
1211
|
+
*
|
|
1212
|
+
* Unref'd: this timer exists to refresh a UI that is already running, and a Host
|
|
1213
|
+
* with nothing else pending (no window attached, no turn) may exit instead — the
|
|
1214
|
+
* expiry is evaluated against the timestamp on the next frame anyway, so nothing
|
|
1215
|
+
* depends on the callback being reached.
|
|
1216
|
+
*/
|
|
1217
|
+
private armFeedbackExpiry;
|
|
1218
|
+
/** The footer echo the reader can see right now, if any (a test/`/diag` seam). */
|
|
1219
|
+
currentFooterEcho(): string | undefined;
|
|
1220
|
+
/** The ephemeral notice the reader can see right now, if any. */
|
|
1221
|
+
currentNotice(): string | undefined;
|
|
1222
|
+
/** The audit as text, for the dev script and for `/diag`-style reporting. */
|
|
1223
|
+
formatRepresentationAudit(): string;
|
|
1224
|
+
/** The rows the reader can see: everything after the `/clear` cutoff. */
|
|
1225
|
+
private visibleRows;
|
|
761
1226
|
private workspaceRowsFor;
|
|
762
|
-
|
|
1227
|
+
/**
|
|
1228
|
+
* Open one report as a Screen.
|
|
1229
|
+
*
|
|
1230
|
+
* A report is not narrative: it cannot be rebuilt from the session log (it is not
|
|
1231
|
+
* a session event), so leaving it in the transcript meant the reader scrolled
|
|
1232
|
+
* through something that would vanish on the next resume, pushed the real history
|
|
1233
|
+
* out of the window (`/help` moved the anchor 53 rows, measured) and cost a full
|
|
1234
|
+
* clear per invocation. It is ephemeral by design: a Screen, gone when dismissed,
|
|
1235
|
+
* never written to the log (AD-7).
|
|
1236
|
+
*
|
|
1237
|
+
* Only the *successful* path comes here. A command that fails still writes an
|
|
1238
|
+
* `error` row: a failure is part of the session's story and has to outlive the
|
|
1239
|
+
* visit.
|
|
1240
|
+
*/
|
|
1241
|
+
private openReport;
|
|
1242
|
+
/**
|
|
1243
|
+
* Open a Screen: it replaces the workspace for as long as it is up.
|
|
1244
|
+
*
|
|
1245
|
+
* The workspace's own state — the transcript window, `scrollOffset`, the focused
|
|
1246
|
+
* card, `/find` hits, the composer draft, and whatever Surface is queued behind
|
|
1247
|
+
* it — is neither touched here nor rebuilt on the way out. A Screen is a visit,
|
|
1248
|
+
* not a second workspace.
|
|
1249
|
+
*/
|
|
1250
|
+
private openScreen;
|
|
1251
|
+
/** Leave the Screen and hand the pixels back to the workspace, as they were. */
|
|
1252
|
+
closeScreen(): void;
|
|
1253
|
+
/**
|
|
1254
|
+
* Ask a confirmation in whichever channel owns the screen.
|
|
1255
|
+
*
|
|
1256
|
+
* A Screen's own action (the doctor repair) needs a human decision, and while a
|
|
1257
|
+
* Screen is up that question belongs to it: it is painted on the Screen's hint
|
|
1258
|
+
* row and settled by the Screen's key handler, so it never enters the workspace
|
|
1259
|
+
* `dialogQueue` and a workspace question waiting there is left untouched (AD-3).
|
|
1260
|
+
* Returns `undefined` when no Screen is up, so the caller uses the ordinary path.
|
|
1261
|
+
*/
|
|
1262
|
+
private askScreenConfirm;
|
|
1263
|
+
/** Settle the Screen's own confirmation, if one is up. */
|
|
1264
|
+
private settleScreenConfirm;
|
|
1265
|
+
/**
|
|
1266
|
+
* Paint the Screen that is up: it replaces the workspace entirely.
|
|
1267
|
+
*
|
|
1268
|
+
* A Screen is state (`ScreenState`) plus this renderer, and nothing else: the
|
|
1269
|
+
* same state redraws the same picture, which is what makes a reattach after an
|
|
1270
|
+
* SSH drop ordinary rather than special. Only the *first* frame of a Screen —
|
|
1271
|
+
* opening, a resize, a reattach — clears the screen; scrolling a report repaints
|
|
1272
|
+
* the body rows that actually changed and leaves the title, hint and strip alone.
|
|
1273
|
+
* The previous implementation passed `sizeChanged: true` on every frame, so every
|
|
1274
|
+
* arrow key cost a full clear and a full frame (measured: 2.7 KB per keypress at
|
|
1275
|
+
* 100×20).
|
|
1276
|
+
*/
|
|
1277
|
+
private paintScreen;
|
|
1278
|
+
/**
|
|
1279
|
+
* The Screen's bottom hint row: how to leave, where the reader is, how to move.
|
|
1280
|
+
*
|
|
1281
|
+
* Composed in that order so truncation takes the least load-bearing part first:
|
|
1282
|
+
* a reader on a 48-column terminal still sees `全文 12–16/80 · Esc 返回`, while a
|
|
1283
|
+
* wide one also gets the navigation keys. A confirmation owned by the Screen, and
|
|
1284
|
+
* a notice from an action it performed, take the row outright — both are answers
|
|
1285
|
+
* to something the reader just did, and both belong to *this* Screen rather than
|
|
1286
|
+
* to the workspace's dialog queue (AD-3).
|
|
1287
|
+
*/
|
|
1288
|
+
private screenHintLine;
|
|
1289
|
+
/**
|
|
1290
|
+
* The setup Screen's rows, built from the wizard's state every frame.
|
|
1291
|
+
*
|
|
1292
|
+
* The order is the contract (B2.6 §8): what this step *is*, then its prose, then the
|
|
1293
|
+
* control the reader acts on, then validation, then the keys. The layout keeps the
|
|
1294
|
+
* last two whatever the height — prose is what gets cut, never the field — and the
|
|
1295
|
+
* secret step renders the key masked, because a Screen is a place other people can
|
|
1296
|
+
* see (B2.6 §4/§8).
|
|
1297
|
+
*/
|
|
1298
|
+
private renderSetupBody;
|
|
1299
|
+
/**
|
|
1300
|
+
* Fit prose and controls into whatever rows are left.
|
|
1301
|
+
*
|
|
1302
|
+
* Under height pressure the *prose* is what goes: the field, the notice and the key
|
|
1303
|
+
* hints are how the reader continues, and a setup screen whose only control was cut
|
|
1304
|
+
* away is a dead end (B2.6 §8).
|
|
1305
|
+
*/
|
|
1306
|
+
private windowSetupBody;
|
|
1307
|
+
/** Rows the setup body may use: the Screen's own layout, minus the step indicator. */
|
|
1308
|
+
private setupBodyBudget;
|
|
1309
|
+
/** A Screen's body, wrapped for the width it will be shown at. */
|
|
1310
|
+
private renderScreenBody;
|
|
763
1311
|
/**
|
|
764
1312
|
* Reverse-video (or a `»` in monochrome) on the overlay row that holds the
|
|
765
1313
|
* current `/find` needle, matching what the transcript does for the same hit.
|
|
@@ -832,20 +1380,83 @@ export declare class SshTui {
|
|
|
832
1380
|
private runFindCommand;
|
|
833
1381
|
private stepSearch;
|
|
834
1382
|
private paint;
|
|
1383
|
+
/**
|
|
1384
|
+
* The dock line for a terminal with no room for the card.
|
|
1385
|
+
*
|
|
1386
|
+
* `compact` keeps the current step (the one thing a reader acts on) and `minimal`
|
|
1387
|
+
* keeps only the mode and the progress. Both stay one row: the dock is under height
|
|
1388
|
+
* pressure precisely because the workspace is small, and a second row would come out
|
|
1389
|
+
* of the transcript.
|
|
1390
|
+
*/
|
|
1391
|
+
private planDockCompactLine;
|
|
1392
|
+
/**
|
|
1393
|
+
* The runtime facts the footer and a Screen's compact strip both report.
|
|
1394
|
+
*
|
|
1395
|
+
* One derivation, two readers. The Screen strip could have read `this.*` for
|
|
1396
|
+
* itself, but then a new activity kind would have to be taught to two assemblers
|
|
1397
|
+
* and the two surfaces could disagree about whether the agent is running — the
|
|
1398
|
+
* second truth source the decision record forbids (AD-8). The two parameters are
|
|
1399
|
+
* the composer's *shape*, the one input that is a property of the frame rather
|
|
1400
|
+
* than of the session; the strip does not read them.
|
|
1401
|
+
*/
|
|
1402
|
+
private footerFacts;
|
|
1403
|
+
private paintFrame;
|
|
835
1404
|
private workspaceCwd;
|
|
836
1405
|
private announceWorkspaceCwd;
|
|
837
1406
|
private buildSuggestions;
|
|
838
1407
|
private suggestionsVisible;
|
|
839
1408
|
private currentProviderId;
|
|
840
1409
|
private currentSelectionLabel;
|
|
841
|
-
/**
|
|
842
|
-
|
|
1410
|
+
/**
|
|
1411
|
+
* The session's own telemetry, in the shapes the old status line used.
|
|
1412
|
+
*
|
|
1413
|
+
* Kept because the footer is not the only reader: the numbers a user files a
|
|
1414
|
+
* bug report with (turns, steps, model time, tool time, cache hit, and the
|
|
1415
|
+
* exact per-step decode rate) are still part of the session's public surface,
|
|
1416
|
+
* and this is the accessor `/status` and the tests reach for. The default row
|
|
1417
|
+
* itself no longer shows any of it.
|
|
1418
|
+
*/
|
|
1419
|
+
statsText(): string;
|
|
1420
|
+
/**
|
|
1421
|
+
* When the activity the status row is reporting began.
|
|
1422
|
+
*
|
|
1423
|
+
* Each state has its own natural clock, and the row shows whichever one
|
|
1424
|
+
* belongs to the state it is painting: a tool shows how long that tool has
|
|
1425
|
+
* run, a subagent the age of the oldest child, a compaction its card, and a
|
|
1426
|
+
* streaming step the time since its first delta. A state with no honest clock
|
|
1427
|
+
* — plan mode, a goal, a question nobody has answered yet — gets none, so the
|
|
1428
|
+
* chip shows the verb alone rather than borrowing a number that measures
|
|
1429
|
+
* something else.
|
|
1430
|
+
*/
|
|
1431
|
+
private footerActivityStartedAt;
|
|
1432
|
+
/**
|
|
1433
|
+
* What the open tool calls are, as one word for the activity chip.
|
|
1434
|
+
*
|
|
1435
|
+
* One call names itself through the transcript's own tool vocabulary
|
|
1436
|
+
* (`toolTitle`, the same table the cards use, so `bash` reads `terminal` in
|
|
1437
|
+
* both places). Several at once get the count instead, because "which one" has
|
|
1438
|
+
* no single answer and a comma-separated list is not a status chip.
|
|
1439
|
+
*/
|
|
1440
|
+
private activityToolLabel;
|
|
1441
|
+
private agentStatusRunning;
|
|
843
1442
|
/** Refresh the terminal window title (throttled while running). */
|
|
844
1443
|
private updateTerminalTitle;
|
|
845
1444
|
/** Terminal bell on completion (opt out with DSH_TUI_NO_BELL=1). */
|
|
846
1445
|
private playCompletionSignal;
|
|
847
1446
|
private render;
|
|
848
1447
|
/** Whether the previous frames have not drained yet (slow link, big burst). */
|
|
1448
|
+
/**
|
|
1449
|
+
* Whether the last frame is still on its way to the terminal.
|
|
1450
|
+
*
|
|
1451
|
+
* Two wires, one question. A direct TTY is `stdout`; a relayed session writes
|
|
1452
|
+
* into the display socket, and that socket is the one that knows how much has
|
|
1453
|
+
* not left yet. The relay path used to answer "no, never backlogged", which left
|
|
1454
|
+
* the Host with no way to tell a link that is keeping up from one that is not —
|
|
1455
|
+
* so it paced itself from the measured round trip alone, and a drag on a fast
|
|
1456
|
+
* link waited for a cadence nobody needed.
|
|
1457
|
+
*/
|
|
1458
|
+
private outputBacklogged;
|
|
1459
|
+
/** The name the paint path has always used for this question. */
|
|
849
1460
|
private stdoutBacklogged;
|
|
850
1461
|
/**
|
|
851
1462
|
* `/preset`: list, inspect, copy, rename, describe, and delete agent presets.
|
|
@@ -963,6 +1574,22 @@ export declare class SshTui {
|
|
|
963
1574
|
private readonly handleError;
|
|
964
1575
|
private readonly handleInboxClaimed;
|
|
965
1576
|
private readonly handleInboxDiscarded;
|
|
1577
|
+
/**
|
|
1578
|
+
* Say that a message is queued, and hold the notice until it is really submitted.
|
|
1579
|
+
*
|
|
1580
|
+
* The one notice that must outlive its clock: it is the only sign the reader has that
|
|
1581
|
+
* something is waiting, and it stays true until the step claims it — so it lives as
|
|
1582
|
+
* long as the queue does (`noteQueueDrained` clears it), not as long as a timer says.
|
|
1583
|
+
*/
|
|
1584
|
+
private showQueuedNotice;
|
|
1585
|
+
/**
|
|
1586
|
+
* The last queued message was claimed (or dropped): the acknowledgement goes with it.
|
|
1587
|
+
*
|
|
1588
|
+
* This is what "until it is really submitted" means — the notice is not about the
|
|
1589
|
+
* keystroke that queued the message, it is about the message still waiting, so it
|
|
1590
|
+
* ends where the waiting does.
|
|
1591
|
+
*/
|
|
1592
|
+
private noteQueueDrained;
|
|
966
1593
|
private readonly handleDisposed;
|
|
967
1594
|
/**
|
|
968
1595
|
* One `workspace/changes` event: the files a turn changed.
|
|
@@ -1081,9 +1708,107 @@ export declare class SshTui {
|
|
|
1081
1708
|
* {@link TUI_SOURCE_KIND}.
|
|
1082
1709
|
*/
|
|
1083
1710
|
private tellModelApprovalDenied;
|
|
1711
|
+
/**
|
|
1712
|
+
* The card an approval request guards, marked as *being asked*.
|
|
1713
|
+
*
|
|
1714
|
+
* Waiting is a live-only state: it is true while a Host is holding the request
|
|
1715
|
+
* open, and a replay can never claim it (see `approval/asked`). Set here rather
|
|
1716
|
+
* than in the auto path so a manual prompt and an automatic decision both show
|
|
1717
|
+
* the same thing on the card while the decision is being made.
|
|
1718
|
+
* @param request - the pending approval.
|
|
1719
|
+
* @param provenance - who is deciding: a person (`live`) or this plugin (`policy`).
|
|
1720
|
+
*/
|
|
1721
|
+
private markApprovalWaiting;
|
|
1722
|
+
/**
|
|
1723
|
+
* Record the decision on the card it guarded.
|
|
1724
|
+
*
|
|
1725
|
+
* `provenance` is the strongest source that produced it: `policy` for a rule, a
|
|
1726
|
+
* cached verdict or a reviewer, `live` for the person at the keyboard. The
|
|
1727
|
+
* Harness's own `approval/decided` event follows this on the same turn and says
|
|
1728
|
+
* strictly less, so `mergeApproval` lets this win (B2.4).
|
|
1729
|
+
*/
|
|
1730
|
+
private settleToolApproval;
|
|
1084
1731
|
readonly handleApproval: (request: ApprovalRequest, _next: () => Promise<ApprovalOutcome>) => Promise<ApprovalOutcome>;
|
|
1732
|
+
/**
|
|
1733
|
+
* Answer a question whose window closed but whose call the Session still holds.
|
|
1734
|
+
*
|
|
1735
|
+
* `ctx.userQuestions.answer(agent, callId, batch)` is the Harness's own channel
|
|
1736
|
+
* for this: it steers a `user-question-reply` message into the agent, and that
|
|
1737
|
+
* message is what closes the question in the projection. Two preconditions come
|
|
1738
|
+
* from the service, not from us — the agent must be the session's live runtime
|
|
1739
|
+
* root, and the question must still be `continued` — so a refusal here is
|
|
1740
|
+
* reported rather than retried.
|
|
1741
|
+
* @param row - the focused question card.
|
|
1742
|
+
* @returns true when the card consumed the key.
|
|
1743
|
+
*/
|
|
1744
|
+
private answerContinuedQuestion;
|
|
1745
|
+
/**
|
|
1746
|
+
* The text field a surface borrowed, when the surface needs typed input.
|
|
1747
|
+
*
|
|
1748
|
+
* The composer's draft used to *be* every dialog's text field: a free-text
|
|
1749
|
+
* answer, a setup field and a picker's search all wrote `this.input`. That made
|
|
1750
|
+
* "what does this string mean" depend on which mode happened to be active, and
|
|
1751
|
+
* a paste or a cancelled answer could overwrite the message the reader was
|
|
1752
|
+
* writing. A surface that needs text now gets its own field; the composer keeps
|
|
1753
|
+
* its draft until the composer is the thing being typed into.
|
|
1754
|
+
*/
|
|
1755
|
+
private borrowedText;
|
|
1756
|
+
/** Whether this dialog types into a field of its own rather than the composer. */
|
|
1757
|
+
private dialogNeedsText;
|
|
1758
|
+
/** Give a text-needing dialog its own field. The composer draft stays put. */
|
|
1759
|
+
private borrowTextFor;
|
|
1760
|
+
private releaseBorrowedText;
|
|
1761
|
+
/** The text the keyboard is editing: a borrowed field, or the composer draft. */
|
|
1762
|
+
private fieldText;
|
|
1763
|
+
private fieldCursor;
|
|
1764
|
+
/** Write the field the keyboard is editing, wherever it lives. */
|
|
1765
|
+
private setField;
|
|
1766
|
+
/** Insert at the caret of whichever field owns the keyboard. */
|
|
1767
|
+
private insertIntoField;
|
|
1768
|
+
/** One card's key: a call's batch can repeat a question id across calls. */
|
|
1769
|
+
private questionCardKey;
|
|
1770
|
+
/**
|
|
1771
|
+
* The Session's durable question view, when the projection is registered.
|
|
1772
|
+
*
|
|
1773
|
+
* `ctx.sessionProjections` is driven by `@deepseek-ai/dsh-user-questions`, which
|
|
1774
|
+
* folds `tool/call` + `tool/result` (and late replies) into
|
|
1775
|
+
* `{ active, settled }`. Absent in a bare Context — a test, an embedder without
|
|
1776
|
+
* the projection package — which is why every caller here has a live fallback.
|
|
1777
|
+
*/
|
|
1778
|
+
private questionProjection;
|
|
1779
|
+
/**
|
|
1780
|
+
* Draw the cards the Session log can account for.
|
|
1781
|
+
*
|
|
1782
|
+
* Called for the events that can change question state, live and replayed alike,
|
|
1783
|
+
* so the two paths produce the same transcript. It never removes a card: a card
|
|
1784
|
+
* is history once it is drawn, and a call the projection cannot see (the legacy
|
|
1785
|
+
* blocking schema, or no projection at all) is the live path's to own.
|
|
1786
|
+
*/
|
|
1787
|
+
private syncQuestionRows;
|
|
1788
|
+
/** One durable record onto its card, creating the card the first time. */
|
|
1789
|
+
private applyQuestionRecord;
|
|
1790
|
+
/**
|
|
1791
|
+
* The card for one call's question, created from its structured form if new.
|
|
1792
|
+
*
|
|
1793
|
+
* A **plan review** gets none (B2.5 §7): it is the plan artifact's interaction, not
|
|
1794
|
+
* an ordinary ask, and the artifact already has a row — the same review drawn as
|
|
1795
|
+
* both a question summary and a plan state is the duplicate this round removes. The
|
|
1796
|
+
* Surface (the dialog) is unchanged, and `dsh-plan-mode` identifies the review by the
|
|
1797
|
+
* `exit_plan_mode` call it belongs to, which the projection reads.
|
|
1798
|
+
* @returns the card, or undefined when the question belongs to another artifact.
|
|
1799
|
+
*/
|
|
1800
|
+
private ensureQuestionCard;
|
|
1085
1801
|
readonly handleUserQuestions: (request: AskUserQuestionRequest) => Promise<AskUserQuestionAnswer>;
|
|
1086
|
-
/**
|
|
1802
|
+
/**
|
|
1803
|
+
* Queue one dialog behind an already-open one instead of overwriting it.
|
|
1804
|
+
*
|
|
1805
|
+
* The active surface is never replaced: whoever owns the keyboard keeps it until
|
|
1806
|
+
* it is answered or cancelled, so a picker can never cover a question (and a
|
|
1807
|
+
* question can never silently discard a half-made choice). What priority decides
|
|
1808
|
+
* is the *line*: a task interaction takes the keyboard ahead of a picker that was
|
|
1809
|
+
* already waiting, because someone is blocked on it. Ranks are compared, not
|
|
1810
|
+
* stacked — equal ranks keep arrival order.
|
|
1811
|
+
*/
|
|
1087
1812
|
private openDialog;
|
|
1088
1813
|
private showNextDialog;
|
|
1089
1814
|
/**
|
|
@@ -1099,8 +1824,16 @@ export declare class SshTui {
|
|
|
1099
1824
|
/** Resolve one queued or active confirm from its abort signal. */
|
|
1100
1825
|
private abortConfirm;
|
|
1101
1826
|
private openQuestion;
|
|
1102
|
-
/**
|
|
1827
|
+
/**
|
|
1828
|
+
* Open one control-plane question and await its answer (cancellation rejects).
|
|
1829
|
+
*
|
|
1830
|
+
* Every `/model`-style menu goes through here, which is why the role is
|
|
1831
|
+
* `picker`: the agent is neither waiting nor interrupted by a menu that only
|
|
1832
|
+
* changes the environment.
|
|
1833
|
+
*/
|
|
1103
1834
|
private askQuestion;
|
|
1835
|
+
/** One question dialog with an explicit role, awaited by the caller. */
|
|
1836
|
+
private openQuestionAs;
|
|
1104
1837
|
/** The stored llm-pi-ai profile for one provider route, when settings provide one. */
|
|
1105
1838
|
private piAiProviderProfile;
|
|
1106
1839
|
/**
|
|
@@ -1396,11 +2129,57 @@ export declare class SshTui {
|
|
|
1396
2129
|
private resolveSuperGrokToken;
|
|
1397
2130
|
private fetchJson;
|
|
1398
2131
|
private readonly handleData;
|
|
2132
|
+
/**
|
|
2133
|
+
* One key, handled by the Screen that is up.
|
|
2134
|
+
*
|
|
2135
|
+
* The Screen owns the keyboard completely, so this is also the place that keeps
|
|
2136
|
+
* the workspace's state out of reach: no arrow moves a card behind the Screen, no
|
|
2137
|
+
* page key scrolls the transcript, no print reaches the composer draft. Closing
|
|
2138
|
+
* keys come from `inspectClosesOn`, which is what the overlay always accepted —
|
|
2139
|
+
* plus `q`, which a full-screen report is expected to answer to.
|
|
2140
|
+
*/
|
|
2141
|
+
private handleScreenInput;
|
|
2142
|
+
/**
|
|
2143
|
+
* Whether anything human is waiting behind the Screen that is up.
|
|
2144
|
+
*
|
|
2145
|
+
* A Surface cannot open while a Screen owns the screen (it stays in the workspace
|
|
2146
|
+
* queue), so without this the reader would have no way to know that an approval
|
|
2147
|
+
* or a question is waiting for them behind the report — the one thing a Screen
|
|
2148
|
+
* must never hide (AD-8). A question the Session still holds counts too: the agent
|
|
2149
|
+
* is not blocked, but the reader has something to answer.
|
|
2150
|
+
*/
|
|
2151
|
+
private screenWaitingWork;
|
|
2152
|
+
/** Rows one PageUp moves a Screen: the body, less one row of context. */
|
|
2153
|
+
private screenPage;
|
|
2154
|
+
/** Scroll a Screen by `up` rows toward the start of its body. */
|
|
2155
|
+
private scrollScreen;
|
|
2156
|
+
private scrollScreenTo;
|
|
2157
|
+
/** The copy key inside a Screen: what the Screen shows is what the reader means. */
|
|
2158
|
+
private copyFromScreen;
|
|
1399
2159
|
private handleInputText;
|
|
1400
2160
|
/** Handle one data chunk that may contain bracketed-paste markers. */
|
|
1401
2161
|
private processPasteChunk;
|
|
1402
|
-
/**
|
|
2162
|
+
/**
|
|
2163
|
+
* Insert pasted text into the field that owns the keyboard; CR/LF are literal
|
|
2164
|
+
* newlines.
|
|
2165
|
+
*
|
|
2166
|
+
* A paste is text input, so it goes through the same ownership question ordinary
|
|
2167
|
+
* typing does: into the composer's draft when the composer owns the keyboard, and
|
|
2168
|
+
* into a borrowed field when a surface that has one does. A surface without a
|
|
2169
|
+
* text field (a confirmation, the inspect overlay) takes no paste at all — the
|
|
2170
|
+
* alternative was writing the composer's hidden draft behind it, which is how a
|
|
2171
|
+
* paste into an approval dialog could silently rewrite the message being written.
|
|
2172
|
+
*/
|
|
1403
2173
|
private handlePasteText;
|
|
2174
|
+
/**
|
|
2175
|
+
* Whether the keys that edit text belong to a field right now.
|
|
2176
|
+
*
|
|
2177
|
+
* The composer always has one; an open surface only has one when it borrowed it
|
|
2178
|
+
* (a free-text answer, a setup field). Everything else — a confirmation, a menu,
|
|
2179
|
+
* the inspect overlay — owns the keyboard without owning text, and those keys
|
|
2180
|
+
* must do nothing rather than reach the draft behind the surface.
|
|
2181
|
+
*/
|
|
2182
|
+
private textIsEditable;
|
|
1404
2183
|
private handlePlainText;
|
|
1405
2184
|
private handleChar;
|
|
1406
2185
|
/**
|
|
@@ -1442,6 +2221,33 @@ export declare class SshTui {
|
|
|
1442
2221
|
private onboardingWantsEndpoint;
|
|
1443
2222
|
/** Fill the picks' capacities and endpoints from the gateway; never fatal. */
|
|
1444
2223
|
private enrichOnboardingModels;
|
|
2224
|
+
/** The wizard's field text, from the wizard's own state. */
|
|
2225
|
+
private setupField;
|
|
2226
|
+
/** Replace the wizard's field and caret; the composer is not touched. */
|
|
2227
|
+
private setupSetField;
|
|
2228
|
+
/** Insert into the wizard's field at its caret. */
|
|
2229
|
+
private setupInsert;
|
|
2230
|
+
/** Delete the code point before the wizard's caret. */
|
|
2231
|
+
private setupBackspace;
|
|
2232
|
+
/**
|
|
2233
|
+
* Say something inside the setup Screen.
|
|
2234
|
+
*
|
|
2235
|
+
* Framed mode keeps it on the Screen's own message row — the wizard is a screen,
|
|
2236
|
+
* not history, and a `Step 1 done` transcript would be a record of a flow that
|
|
2237
|
+
* only ever ran once (B2.6 §12/§13). Line mode has no screen to say it on and has
|
|
2238
|
+
* always printed the wizard's messages as text, so it still appends one.
|
|
2239
|
+
*/
|
|
2240
|
+
private setupMessage;
|
|
2241
|
+
/**
|
|
2242
|
+
* The setup Screen's key handling: its own field, its own navigation.
|
|
2243
|
+
*
|
|
2244
|
+
* Everything the wizard used to get from the workspace's composer and its
|
|
2245
|
+
* dialog key path is handled here — the caret keys edit the wizard's field, the
|
|
2246
|
+
* arrows move the wizard's pickers, Esc steps back through the picker steps or
|
|
2247
|
+
* abandons the flow, and the confirm step answers itself. A key this does not
|
|
2248
|
+
* consume is *not* forwarded anywhere: a setup screen has no other owner (B2.6 §4).
|
|
2249
|
+
*/
|
|
2250
|
+
private handleSetupKey;
|
|
1445
2251
|
private handleOnboardingChar;
|
|
1446
2252
|
private advanceOnboarding;
|
|
1447
2253
|
/**
|
|
@@ -1491,6 +2297,8 @@ export declare class SshTui {
|
|
|
1491
2297
|
private plainSelectableLines;
|
|
1492
2298
|
/** Screen row from a mouse report to an index into `selectableLines`. */
|
|
1493
2299
|
private selectableLineAt;
|
|
2300
|
+
/** The last transcript row the interaction does not cover, or the whole list. */
|
|
2301
|
+
private lastSelectableLine;
|
|
1494
2302
|
/**
|
|
1495
2303
|
* Left button pressed. The click itself waits for the release: this may turn
|
|
1496
2304
|
* into a drag, and a press that never moves must still open a link or toggle
|
|
@@ -1505,8 +2313,48 @@ export declare class SshTui {
|
|
|
1505
2313
|
private highlightSelection;
|
|
1506
2314
|
handleMouseClick(y: number, x?: number): void;
|
|
1507
2315
|
private copyPlainText;
|
|
2316
|
+
/**
|
|
2317
|
+
* `/copy [reply|highlight|error]` — copy plain text to the local clipboard.
|
|
2318
|
+
*
|
|
2319
|
+
* The default is the model's **last reply**, which is what a reader wants on
|
|
2320
|
+
* the clipboard most of the time: the answer they just read, whole, ready to
|
|
2321
|
+
* paste into a report. It used to be "the highlighted card, else the newest
|
|
2322
|
+
* reply", so any card left highlighted (one click on a tool card does it)
|
|
2323
|
+
* silently redirected the copy and the reply became unreachable without
|
|
2324
|
+
* deselecting first. `highlight` is that old rule, now said out loud.
|
|
2325
|
+
*/
|
|
1508
2326
|
copyFocusedCard(arg?: string): boolean;
|
|
1509
|
-
|
|
2327
|
+
/**
|
|
2328
|
+
* The copy key: what the reader is *pointing at*.
|
|
2329
|
+
*
|
|
2330
|
+
* The key and the command are two grammars over the same act, and they differ
|
|
2331
|
+
* in exactly one place: with nothing highlighted the key falls back to the
|
|
2332
|
+
* newest reply (a key that copies nothing reads as a broken key), while
|
|
2333
|
+
* `/copy highlight` says there is nothing highlighted. Inside a Surface the key
|
|
2334
|
+
* is the only way to copy at all: a Screen carries what it shows, and every
|
|
2335
|
+
* other dialog keeps the key inert, exactly as it always was.
|
|
2336
|
+
*/
|
|
2337
|
+
private copyKey;
|
|
2338
|
+
/** The newest error or diagnostic row, whole. */
|
|
2339
|
+
private copyErrorRow;
|
|
2340
|
+
/** The highlighted card — or, for the key, the newest reply when none is. */
|
|
2341
|
+
private copyHighlighted;
|
|
2342
|
+
/** The model's last reply: the `/copy` default, and the key's last resort. */
|
|
2343
|
+
private copyLatestReply;
|
|
2344
|
+
/**
|
|
2345
|
+
* Scroll whichever surface the reader is looking at, by `up` lines toward the
|
|
2346
|
+
* earlier content.
|
|
2347
|
+
*
|
|
2348
|
+
* One sign for every caller, because the two surfaces count in opposite
|
|
2349
|
+
* directions: the transcript's `scrollOffset` is the distance *back* from the
|
|
2350
|
+
* newest line, and the overlay's `offset` is the index of its first visible line.
|
|
2351
|
+
* Adding one delta to both — which is what this did — made PgUp and the wheel
|
|
2352
|
+
* scroll the overlay the wrong way, while the arrow keys, which passed their own
|
|
2353
|
+
* inverted sign, were the only ones that felt right. A surface that is not the
|
|
2354
|
+
* inspect overlay owns the keyboard and takes no scroll at all: the transcript
|
|
2355
|
+
* behind a dialog must not move.
|
|
2356
|
+
*/
|
|
2357
|
+
private scrollActiveSurface;
|
|
1510
2358
|
private handleCtrlC;
|
|
1511
2359
|
private submit;
|
|
1512
2360
|
private runCommand;
|
|
@@ -1514,8 +2362,11 @@ export declare class SshTui {
|
|
|
1514
2362
|
private graphemeBefore;
|
|
1515
2363
|
/** Grapheme cluster containing or following `cursor`. */
|
|
1516
2364
|
private graphemeAfter;
|
|
2365
|
+
/** Delete one grapheme back, in whichever field owns the keyboard. */
|
|
1517
2366
|
private backspace;
|
|
2367
|
+
/** Delete one grapheme forward, in whichever field owns the keyboard. */
|
|
1518
2368
|
private deleteAtCursor;
|
|
2369
|
+
/** Move the caret one grapheme, in whichever field owns the keyboard. */
|
|
1519
2370
|
private moveCursor;
|
|
1520
2371
|
private historyBack;
|
|
1521
2372
|
private historyForward;
|