@aragon-agent/cli 0.6.32 → 0.6.34
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 +2305 -2223
- package/README.md +3007 -2957
- package/dist/cli.js +4 -0
- package/dist/commands/perf.d.ts +34 -1
- package/dist/commands/perf.js +11 -3
- package/dist/ui/App.js +64 -12
- package/dist/ui/ScrollbackShell.d.ts +48 -13
- package/dist/ui/ScrollbackShell.js +7 -3
- package/dist/ui/scrollback/LiveTail.d.ts +64 -15
- package/dist/ui/scrollback/LiveTail.js +27 -11
- package/dist/ui/scrollback/band.d.ts +33 -24
- package/dist/ui/scrollback/band.js +43 -22
- package/dist/ui/scrollback/budget.d.ts +31 -19
- package/dist/ui/scrollback/budget.js +37 -25
- package/dist/ui/scrollback/limits.d.ts +34 -10
- package/dist/ui/scrollback/limits.js +34 -10
- package/dist/ui/scrollback/live-preview.d.ts +18 -10
- package/dist/ui/scrollback/live-preview.js +14 -11
- package/dist/ui/scrollback/overflow-print.d.ts +108 -0
- package/dist/ui/scrollback/overflow-print.js +120 -0
- package/dist/ui/scrollback/renderer.d.ts +11 -1
- package/dist/ui/scrollback/renderer.js +53 -25
- package/dist/ui/scrollback/tail-anchor.d.ts +68 -0
- package/dist/ui/scrollback/tail-anchor.js +77 -0
- package/dist/ui/scrollback/tail-window.d.ts +61 -0
- package/dist/ui/scrollback/tail-window.js +74 -0
- package/dist/ui/scrollback/use-scrollback-view.d.ts +60 -24
- package/dist/ui/scrollback/use-scrollback-view.js +434 -134
- package/package.json +2 -2
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The TAIL WINDOW of the native-scroll live region
|
|
3
|
+
* (tui-full-height-live-region D6 / D7, I-F8 / I-F9).
|
|
4
|
+
*
|
|
5
|
+
* WHAT CHANGED AND WHY. The region used to show only the entries the reducer had not
|
|
6
|
+
* finished writing (`entries.slice(settledCount)`), and everything settled was printed
|
|
7
|
+
* into the terminal's scrollback the moment it settled. Once the region is the WHOLE
|
|
8
|
+
* screen (`liveRows = rows - 1`), a printed row is pushed off the top by the very next
|
|
9
|
+
* frame, so "print on settle" would leave the user staring at a screen of blanks with
|
|
10
|
+
* only the unfinished cards on it. So the region now shows the last `K` PRINTABLE
|
|
11
|
+
* entries - settled or not - and an entry is printed when it LEAVES that window.
|
|
12
|
+
*
|
|
13
|
+
* The window is defined by ENTRY COUNT, never by measured height; what a single entry
|
|
14
|
+
* taller than the region used to do is the whole subject of
|
|
15
|
+
* `overflow-print.ts` (tui-final-reply-truncated-unscrollable). The box still clips it
|
|
16
|
+
* from the top with `justifyContent="flex-end"`, but the rows it hides are now PRINTED
|
|
17
|
+
* into the terminal's scrollback, where the wheel already reaches, instead of waiting for
|
|
18
|
+
* `K` further entries (README, known limitation 2 - rewritten by that fix).
|
|
19
|
+
*
|
|
20
|
+
* PURE: no Ink, no I/O, no clock. `promote()` (the writer's only feeder) takes
|
|
21
|
+
* `printBoundary`'s result and keeps its own `printed` authority, so "printed is
|
|
22
|
+
* forward-only" (RV-7) is unchanged.
|
|
23
|
+
*
|
|
24
|
+
* PRINTABILITY != VISIBILITY. `isExcluded` entries (a non-terminal `service` card) never
|
|
25
|
+
* print and never take a slot in the window; an entry that is in the window may still be
|
|
26
|
+
* scrolled out of sight by a taller neighbour. That second gap is what the row-level plan
|
|
27
|
+
* closes, and `/perf`'s `window=<start>/<n> head=<rows>` is how it stays observable.
|
|
28
|
+
*/
|
|
29
|
+
import { isExcluded, settledCount } from './settled-boundary.js';
|
|
30
|
+
/**
|
|
31
|
+
* The index of the first entry that is still on screen: count backwards up to
|
|
32
|
+
* `maxEntries` PRINTABLE entries. Everything before it has left the region and is the
|
|
33
|
+
* writer's business.
|
|
34
|
+
*
|
|
35
|
+
* `maxEntries <= 0` returns `entries.length` - an empty window, which is the old
|
|
36
|
+
* behaviour ("everything printable may print") and the reason the guard is written as
|
|
37
|
+
* an early return rather than as a loop bound.
|
|
38
|
+
*/
|
|
39
|
+
export function tailWindowStart(entries, maxEntries) {
|
|
40
|
+
const limit = Number.isFinite(maxEntries) ? Math.floor(maxEntries) : 0;
|
|
41
|
+
if (limit <= 0)
|
|
42
|
+
return entries.length;
|
|
43
|
+
let seen = 0;
|
|
44
|
+
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
45
|
+
if (isExcluded(entries[i]))
|
|
46
|
+
continue;
|
|
47
|
+
seen += 1;
|
|
48
|
+
if (seen === limit)
|
|
49
|
+
return i;
|
|
50
|
+
}
|
|
51
|
+
// Fewer printable entries than the window holds: the whole conversation is on screen.
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
/** `entries.slice(start)` with the entries that never print dropped. */
|
|
55
|
+
export function selectTailWindow(entries, maxEntries) {
|
|
56
|
+
const start = tailWindowStart(entries, maxEntries);
|
|
57
|
+
return { start, entries: entries.slice(start).filter((entry) => !isExcluded(entry)) };
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The print boundary: never print what the region is still showing.
|
|
61
|
+
*
|
|
62
|
+
* THIS IS THE ONLY PLACE THAT COMBINES "settled" WITH "in the window" (RV-P2-13), which
|
|
63
|
+
* is why the composition lives here instead of at the pump: `promote()` must keep
|
|
64
|
+
* receiving one number, and the two authorities it is derived from - `settledCount`
|
|
65
|
+
* (what may print at all) and `tailWindowStart` (what is still visible) - then cannot
|
|
66
|
+
* drift apart in a future edit.
|
|
67
|
+
*
|
|
68
|
+
* `settleAllEntries` is the OTHER authority, not a duplicate: it answers "what must be
|
|
69
|
+
* printed when the conversation is being torn down", while this answers "how far may
|
|
70
|
+
* this frame print".
|
|
71
|
+
*/
|
|
72
|
+
export function printBoundary(entries, runActive, maxEntries) {
|
|
73
|
+
return Math.min(settledCount(entries, runActive), tailWindowStart(entries, maxEntries));
|
|
74
|
+
}
|
|
@@ -1,31 +1,55 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `useScrollbackView` — the promotion pipeline of native-scroll mode
|
|
3
|
-
* (tui-native-terminal-scroll §4.4 / §6.2
|
|
3
|
+
* (tui-native-terminal-scroll §4.4 / §6.2, tui-full-height-live-region D3/D4/D7/D8,
|
|
4
|
+
* tui-final-reply-truncated-unscrollable A-prime).
|
|
4
5
|
*
|
|
5
6
|
* `App` calls it ALWAYS (hooks cannot be conditional); `enabled: false` makes every
|
|
6
7
|
* effect a no-op and keeps `scrollMode: 'app'` byte-identical to the pre-feature
|
|
7
|
-
* build (I-S9).
|
|
8
|
+
* build (I-S9 / I-F10).
|
|
8
9
|
*
|
|
9
10
|
* What it does when enabled:
|
|
10
|
-
* 1.
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* 1. On every entries change, promotes each entry that has LEFT THE TAIL WINDOW:
|
|
12
|
+
* renders it once through the offscreen renderer and enqueues the rows for the
|
|
13
|
+
* writer. Entries still on screen are never printed - at `H = rows - 1` a printed
|
|
14
|
+
* row is pushed back off the top by the next frame, and a second copy in the
|
|
15
|
+
* scrollback would be a duplicate the user meets by scrolling up (I-F8).
|
|
16
|
+
* 2. Gives way at the TOP (tui-final-reply-truncated-unscrollable). The window is still
|
|
17
|
+
* counted in ENTRIES, so a settled answer taller than the box used to sit inside the
|
|
18
|
+
* window forever, clipped by `justifyContent="flex-end"`, in a mode whose wheel only
|
|
19
|
+
* scrolls the terminal's scrollback: the clipped rows existed nowhere the user could
|
|
20
|
+
* reach. The rows the box hides are now measured (`overflow-print.ts`) and printed, so
|
|
21
|
+
* `scrollback + screen` covers every row exactly once. Screen unchanged, reachability
|
|
22
|
+
* fixed.
|
|
23
|
+
* 3. Computes the live-region budget, the window and the tail rows for the shell.
|
|
24
|
+
* 4. Flushes the window before either reset path (`/clear`, `--resume`) and on exit,
|
|
25
|
+
* so "on screen but not printed yet" can never become "lost" (D8 / I-F12).
|
|
26
|
+
*
|
|
27
|
+
* THE HEADER AND THE OPENER ARE NOT PRINTED HERE ANY MORE (D3/D4). They are slots of
|
|
28
|
+
* `ScrollbackShell` now, rendered inside the region: `H = rows - 1` means any printed
|
|
29
|
+
* row is immediately scrolled off the screen, so printing the banner would be the same
|
|
30
|
+
* as not drawing it. Their `renderNode` path, the `startedRef` latch that kept their
|
|
31
|
+
* element identities stable, and the `printBanner()` call in `settleAll()` are gone.
|
|
14
32
|
*
|
|
15
33
|
* THE PROMOTION RUNS IN A MICROTASK, NOT IN THE EFFECT. The offscreen renderer is a
|
|
16
34
|
* second Ink instance, and both instances share one React reconciler. A `rerender`
|
|
17
35
|
* issued from inside the main tree's commit (a layout effect) is deferred until that
|
|
18
|
-
* commit finishes, so its frame would not exist when the effect reads it. A
|
|
19
|
-
*
|
|
20
|
-
*
|
|
36
|
+
* commit finishes, so its frame would not exist when the effect reads it. A microtask
|
|
37
|
+
* runs after the commit has unwound, where the offscreen render is synchronous again.
|
|
38
|
+
*
|
|
39
|
+
* THE PASS READS THE COMMITTED FRAME, NOT THE LATEST RENDER. `latest` is refreshed during
|
|
40
|
+
* render, so it can be a frame ahead of the screen; `committed` is written by a layout
|
|
41
|
+
* effect and therefore describes exactly the frame the user is looking at. With
|
|
42
|
+
* entry-granular bookkeeping the difference was harmless (printing was idempotent per
|
|
43
|
+
* entry); with ROW-granular bookkeeping it would slice this frame's picture with another
|
|
44
|
+
* frame's row numbers, and the result is a duplicate or a hole.
|
|
21
45
|
*
|
|
22
|
-
* `
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* rescan and let
|
|
46
|
+
* `printed` is the ONLY authority on "already printed" (RV-7): a `Map` from entry id to
|
|
47
|
+
* the leading rows already handed to the writer, and the width they were measured at. The
|
|
48
|
+
* cursor is just a hint for where to resume scanning: entries leave the list by retain-ring
|
|
49
|
+
* trimming AND by reducer deletion (empty settled assistants, abandoned tool cards), so an
|
|
50
|
+
* id-keyed cursor can dangle, and then the only correct answer is to rescan and let
|
|
51
|
+
* `printed` skip.
|
|
27
52
|
*/
|
|
28
|
-
import React from 'react';
|
|
29
53
|
import type { Entry } from '../../agent/reducer.js';
|
|
30
54
|
import type { TermCapabilities } from '../capabilities.js';
|
|
31
55
|
import type { DensityMode } from '../density.js';
|
|
@@ -41,7 +65,7 @@ export interface ScrollbackBridge {
|
|
|
41
65
|
requestFlush: () => void;
|
|
42
66
|
/**
|
|
43
67
|
* Installed by the hook on mount (reads the latest entries through a ref).
|
|
44
|
-
* `restore()` calls it on every exit path; it is idempotent through `
|
|
68
|
+
* `restore()` calls it on every exit path; it is idempotent through `printed`
|
|
45
69
|
* and it releases the offscreen renderer when done.
|
|
46
70
|
*/
|
|
47
71
|
settleAllNow: () => void;
|
|
@@ -68,10 +92,6 @@ export interface UseScrollbackViewArgs {
|
|
|
68
92
|
/** Terminal size. */
|
|
69
93
|
rows: number;
|
|
70
94
|
cols: number;
|
|
71
|
-
/** Printed first, once. */
|
|
72
|
-
headerNode: React.ReactNode;
|
|
73
|
-
/** Printed after the header for an empty session. */
|
|
74
|
-
openerNode: React.ReactNode | null;
|
|
75
95
|
overlayOpen: boolean;
|
|
76
96
|
draftRows: number;
|
|
77
97
|
popupRows: number;
|
|
@@ -84,18 +104,34 @@ export interface UseScrollbackViewArgs {
|
|
|
84
104
|
panelEnabled: boolean;
|
|
85
105
|
/** `buildTeamPanelLayout().rowCount`; 0 when nothing is dispatched. */
|
|
86
106
|
teamRowsNatural: number;
|
|
107
|
+
/**
|
|
108
|
+
* `state.expandedToolIds` - REQUIRED, because the height of a window entry depends on it
|
|
109
|
+
* and a forgotten value would measure an expansion the region is not drawing.
|
|
110
|
+
*/
|
|
111
|
+
expandedToolIds: Record<string, true>;
|
|
87
112
|
}
|
|
88
113
|
export interface ScrollbackView {
|
|
89
114
|
budget: ScrollbackBudget | null;
|
|
90
115
|
liveTailLines: string[];
|
|
91
116
|
/**
|
|
92
|
-
* The
|
|
93
|
-
*
|
|
117
|
+
* The entries the region is showing, in order (excluded kinds already dropped).
|
|
118
|
+
* `windowStart` is their index in the full list - together they are what `/perf`
|
|
119
|
+
* reports as `window=<start>/<n>`, the number that makes the "visible vs printable"
|
|
120
|
+
* gap observable instead of a guess (known limitation 5).
|
|
121
|
+
*/
|
|
122
|
+
tailEntries: readonly Entry[];
|
|
123
|
+
windowStart: number;
|
|
124
|
+
/**
|
|
125
|
+
* Measured height of `tailEntries`, once every one of them is stable AND measured at the
|
|
126
|
+
* current geometry; `undefined` otherwise. `LiveTail` prefers it over the estimate - see
|
|
127
|
+
* `tail-anchor.ts::resolveTailAnchor` for why the measured answer has to win.
|
|
94
128
|
*/
|
|
95
|
-
|
|
129
|
+
measuredTailRows: number | undefined;
|
|
130
|
+
/** Rows the top give-way has printed this session; `/perf`'s `head=`. */
|
|
131
|
+
headRows: number;
|
|
96
132
|
/** Print a fresh EXPANDED copy of an entry; false when it cannot be found / rendered. */
|
|
97
133
|
reprintExpanded(entryId: string): boolean;
|
|
98
|
-
/** Promote everything
|
|
134
|
+
/** Promote everything still unprinted (shutdown) - `flushUnprinted(latest entries)`. */
|
|
99
135
|
settleAll(): void;
|
|
100
136
|
/** Called by `App`'s dispatch wrapper BEFORE a reset action reaches the reducer. */
|
|
101
137
|
notifyReset(kind: ScrollbackResetKind): void;
|