@aragon-agent/cli 0.6.2 → 0.6.4
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 +321 -0
- package/README.md +297 -2
- package/dist/agent/controller.d.ts +193 -1
- package/dist/agent/controller.js +475 -2
- package/dist/agent/headless.d.ts +8 -0
- package/dist/agent/headless.js +47 -0
- package/dist/agent/reducer.d.ts +189 -0
- package/dist/agent/reducer.js +301 -2
- package/dist/agent/system-prompt.d.ts +14 -0
- package/dist/agent/system-prompt.js +2 -0
- package/dist/cli.js +297 -16
- package/dist/commands/builtins.js +165 -24
- package/dist/commands/registry.d.ts +27 -0
- package/dist/compaction/archive.d.ts +175 -0
- package/dist/compaction/archive.js +267 -0
- package/dist/compaction/child.d.ts +97 -0
- package/dist/compaction/child.js +118 -0
- package/dist/compaction/command.d.ts +41 -0
- package/dist/compaction/command.js +355 -0
- package/dist/compaction/compactor.d.ts +302 -0
- package/dist/compaction/compactor.js +810 -0
- package/dist/compaction/digest.d.ts +62 -0
- package/dist/compaction/digest.js +164 -0
- package/dist/compaction/limits.d.ts +165 -0
- package/dist/compaction/limits.js +172 -0
- package/dist/compaction/pressure.d.ts +135 -0
- package/dist/compaction/pressure.js +167 -0
- package/dist/compaction/summarize-call.d.ts +70 -0
- package/dist/compaction/summarize-call.js +86 -0
- package/dist/compaction/summary-prompt.d.ts +85 -0
- package/dist/compaction/summary-prompt.js +197 -0
- package/dist/compaction/tail-budget.d.ts +52 -0
- package/dist/compaction/tail-budget.js +67 -0
- package/dist/compaction/types.d.ts +215 -0
- package/dist/compaction/types.js +22 -0
- package/dist/compaction/wiring.d.ts +264 -0
- package/dist/compaction/wiring.js +577 -0
- package/dist/config/app-paths.d.ts +14 -0
- package/dist/config/app-paths.js +16 -0
- package/dist/config/cli-commands.d.ts +41 -0
- package/dist/config/cli-commands.js +77 -1
- package/dist/config/env.d.ts +5 -2
- package/dist/config/env.js +140 -3
- package/dist/config/load.d.ts +42 -0
- package/dist/config/load.js +101 -1
- package/dist/config/schema.d.ts +244 -0
- package/dist/config/schema.js +170 -0
- package/dist/config/store.js +37 -1
- package/dist/config/ui-state.d.ts +28 -1
- package/dist/config/ui-state.js +9 -0
- package/dist/exec/events.d.ts +50 -1
- package/dist/exec/runner.js +42 -0
- package/dist/input/limits.d.ts +72 -0
- package/dist/input/limits.js +63 -0
- package/dist/input/mouse-events.d.ts +31 -5
- package/dist/input/mouse-events.js +86 -27
- package/dist/input/paste-parse.d.ts +71 -0
- package/dist/input/paste-parse.js +95 -0
- package/dist/input/stdin-filter.d.ts +81 -0
- package/dist/input/stdin-filter.js +379 -0
- package/dist/logging/install.d.ts +53 -1
- package/dist/logging/install.js +81 -1
- package/dist/logging/logger.d.ts +1 -1
- package/dist/proc/classify.d.ts +52 -0
- package/dist/proc/classify.js +143 -0
- package/dist/proc/kill-tree.d.ts +42 -0
- package/dist/proc/kill-tree.js +119 -0
- package/dist/proc/limits.d.ts +84 -0
- package/dist/proc/limits.js +84 -0
- package/dist/proc/log-ring.d.ts +68 -0
- package/dist/proc/log-ring.js +102 -0
- package/dist/proc/prompt.d.ts +24 -0
- package/dist/proc/prompt.js +49 -0
- package/dist/proc/readiness.d.ts +102 -0
- package/dist/proc/readiness.js +219 -0
- package/dist/proc/supervisor.d.ts +103 -0
- package/dist/proc/supervisor.js +465 -0
- package/dist/proc/types.d.ts +175 -0
- package/dist/proc/types.js +15 -0
- package/dist/session/persist.js +55 -1
- package/dist/team/report.js +7 -1
- package/dist/team/runtime.d.ts +10 -0
- package/dist/team/runtime.js +7 -0
- package/dist/team/subagent.d.ts +31 -1
- package/dist/team/subagent.js +42 -15
- package/dist/team/types.d.ts +8 -0
- package/dist/tools/bash-tool.d.ts +51 -2
- package/dist/tools/bash-tool.js +321 -51
- package/dist/tools/fs-tools.d.ts +17 -0
- package/dist/tools/index.d.ts +55 -2
- package/dist/tools/index.js +39 -1
- package/dist/tools/proc-tools.d.ts +25 -0
- package/dist/tools/proc-tools.js +102 -0
- package/dist/ui/ActivityLine.d.ts +19 -1
- package/dist/ui/ActivityLine.js +10 -2
- package/dist/ui/App.d.ts +62 -2
- package/dist/ui/App.js +546 -16
- package/dist/ui/Composer.d.ts +66 -1
- package/dist/ui/Composer.js +50 -6
- package/dist/ui/PromptInput.d.ts +82 -3
- package/dist/ui/PromptInput.js +232 -49
- package/dist/ui/StatusBar.d.ts +59 -0
- package/dist/ui/StatusBar.js +16 -4
- package/dist/ui/TeamPanel.js +7 -1
- package/dist/ui/Transcript.d.ts +21 -1
- package/dist/ui/Transcript.js +61 -3
- package/dist/ui/clipboard.d.ts +52 -0
- package/dist/ui/clipboard.js +91 -0
- package/dist/ui/composer-limits.d.ts +31 -0
- package/dist/ui/composer-limits.js +50 -0
- package/dist/ui/composer-rows.d.ts +73 -0
- package/dist/ui/composer-rows.js +201 -0
- package/dist/ui/editor-reducer.d.ts +37 -0
- package/dist/ui/editor-reducer.js +88 -12
- package/dist/ui/entries/CompactionCard.d.ts +75 -0
- package/dist/ui/entries/CompactionCard.js +158 -0
- package/dist/ui/entries/ServiceCard.d.ts +66 -0
- package/dist/ui/entries/ServiceCard.js +116 -0
- package/dist/ui/entries/UserEntry.d.ts +17 -3
- package/dist/ui/entries/UserEntry.js +34 -5
- package/dist/ui/frame-differ.d.ts +46 -0
- package/dist/ui/frame-differ.js +94 -10
- package/dist/ui/gauge.d.ts +16 -1
- package/dist/ui/gauge.js +25 -6
- package/dist/ui/glyphs.d.ts +11 -0
- package/dist/ui/glyphs.js +2 -0
- package/dist/ui/layout/ScrollViewport.d.ts +43 -6
- package/dist/ui/layout/ScrollViewport.js +128 -21
- package/dist/ui/layout/budget.d.ts +44 -9
- package/dist/ui/layout/budget.js +42 -10
- package/dist/ui/layout/follow-state.d.ts +137 -0
- package/dist/ui/layout/follow-state.js +150 -0
- package/dist/ui/layout/virtual-window.d.ts +11 -0
- package/dist/ui/layout/virtual-window.js +85 -1
- package/dist/ui/overlays/HelpOverlay.js +9 -1
- package/dist/ui/overlays/PlanReviewOverlay.js +10 -2
- package/dist/ui/overlays/QuestionOverlay.js +5 -1
- package/dist/ui/overlays/SettingsScreen.d.ts +47 -1
- package/dist/ui/overlays/SettingsScreen.js +74 -2
- package/dist/ui/palettes.d.ts +16 -0
- package/dist/ui/palettes.js +6 -0
- package/dist/ui/paste-frames.d.ts +87 -0
- package/dist/ui/paste-frames.js +112 -0
- package/dist/ui/paste-tokens.d.ts +90 -0
- package/dist/ui/paste-tokens.js +147 -0
- package/dist/ui/screen.d.ts +38 -0
- package/dist/ui/screen.js +113 -8
- package/dist/ui/selection/highlight.d.ts +39 -0
- package/dist/ui/selection/highlight.js +64 -0
- package/dist/ui/selection/screen-mirror.d.ts +24 -0
- package/dist/ui/selection/screen-mirror.js +35 -0
- package/dist/ui/selection/selection-controller.d.ts +88 -0
- package/dist/ui/selection/selection-controller.js +282 -0
- package/dist/ui/selection/selection.d.ts +90 -0
- package/dist/ui/selection/selection.js +223 -0
- package/dist/ui/stdout-frame-writer.d.ts +33 -0
- package/dist/ui/stdout-frame-writer.js +15 -0
- package/dist/ui/theme.d.ts +11 -0
- package/dist/ui/theme.js +2 -0
- package/dist/ui/transcript-text.js +72 -0
- package/dist/ui/use-startup-notices.d.ts +27 -1
- package/dist/ui/use-startup-notices.js +43 -11
- package/dist/ui/use-wheel-routing.d.ts +1 -1
- package/dist/ui/use-wheel-routing.js +8 -0
- package/package.json +4 -2
- package/dist/input/stdin-mouse-filter.d.ts +0 -42
- package/dist/input/stdin-mouse-filter.js +0 -153
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Follow rules for the self-drawn viewport (tui-selection-and-scroll-follow
|
|
3
|
+
* §4.3). PURE, React-free, unit-tested — the shape `scroll.ts`,
|
|
4
|
+
* `virtual-window.ts` and `frame-differ.ts` all already use.
|
|
5
|
+
*
|
|
6
|
+
* It lives here rather than inside `ScrollViewport` because
|
|
7
|
+
* `ink-testing-library`'s stdout stub reports no height, so a MOUNTED viewport
|
|
8
|
+
* measures `content === viewport`, `overflowLines === 0`, and neither rule below
|
|
9
|
+
* ever engages (`__tests__/render-at-width.ts`, and `transcript-virtual.test.tsx`
|
|
10
|
+
* records the same limitation and the same workaround). A state machine that can
|
|
11
|
+
* only be exercised through a box the test cannot size is a state machine with
|
|
12
|
+
* no tests (P1-10).
|
|
13
|
+
*
|
|
14
|
+
* ═══════════════════════════════════════════════════════════════════════════
|
|
15
|
+
* NOTHING IN THIS FILE MAY READ A MEASUREMENT (AC-17, and it is a `grep`).
|
|
16
|
+
*
|
|
17
|
+
* The gate is a `grep` for the name of Ink's element-measuring helper returning
|
|
18
|
+
* NOTHING from this file, so that name is deliberately left unspelled here and
|
|
19
|
+
* below, even where writing it would read more directly. A grep a COMMENT can
|
|
20
|
+
* trip is a grep whoever runs it learns to ignore, and then the import it exists
|
|
21
|
+
* to catch walks straight past it — the same reason `glyphs.test.ts` strips
|
|
22
|
+
* comments before judging a line.
|
|
23
|
+
*
|
|
24
|
+
* The transcript's MEASURED CONTENT HEIGHT — what `ScrollViewport` reads out of
|
|
25
|
+
* Ink's layout for the inner box — is the height of everything the transcript is
|
|
26
|
+
* currently drawing, and FOUR different things move it — only one of which is
|
|
27
|
+
* "the agent produced output":
|
|
28
|
+
*
|
|
29
|
+
* 1. rows appended at the tail <- Rule A must react
|
|
30
|
+
* 2. an entry ABOVE the reading position changing <- must NOT move
|
|
31
|
+
* height (Ctrl+T, Ctrl+O, a first real measurement
|
|
32
|
+
* of an entry that stood on an estimate)
|
|
33
|
+
* 3. the scroll horizon dropping entries off the <- must NOT move
|
|
34
|
+
* FRONT (`Transcript.tsx`, `entries.slice(-size)`)
|
|
35
|
+
* 4. a re-wrap after a resize <- must NOT move
|
|
36
|
+
*
|
|
37
|
+
* For (2) and (3) `virtual-window.ts`'s invariant V-4 is explicit: counting the
|
|
38
|
+
* offset FROM THE BOTTOM makes an above-the-viewport height change
|
|
39
|
+
* self-compensating, and that is what buys virtualisation its "no
|
|
40
|
+
* scroll-anchoring compensation pass required". Reacting to total content growth
|
|
41
|
+
* would make Rule A that compensation pass, applied to changes that never needed
|
|
42
|
+
* compensating — and it would additionally make (3) look like new output and arm
|
|
43
|
+
* the idle timer on a transcript that produced nothing, the exact failure G6
|
|
44
|
+
* exists to prevent.
|
|
45
|
+
*
|
|
46
|
+
* There is a second, sharper reason. `selectWindow` TAKES `offset` AS AN INPUT,
|
|
47
|
+
* so the mounted set — and therefore which entries are measured rather than
|
|
48
|
+
* estimated — is a function of the offset. An offset that is in turn a function
|
|
49
|
+
* of the measured height closes a loop: offset -> mounted set -> measured
|
|
50
|
+
* heights -> content height -> offset. The loop is broken by the INPUT rather
|
|
51
|
+
* than by a convergence argument: `advanceTailRows` reads the height table and
|
|
52
|
+
* never the geometry context (P0-1 / D-13).
|
|
53
|
+
* ═══════════════════════════════════════════════════════════════════════════
|
|
54
|
+
*/
|
|
55
|
+
import { clampScroll } from './scroll.js';
|
|
56
|
+
/**
|
|
57
|
+
* Rule A. When rows are appended AT THE TAIL while the user is not pinned to the
|
|
58
|
+
* bottom, grow the offset by the same amount: `shiftUp = overflowLines - offset`
|
|
59
|
+
* is then constant and the rows on screen do not move.
|
|
60
|
+
*
|
|
61
|
+
* SIGNED, NOT GROWTH-ONLY (I-7). A tail SHRINK is rows that no longer exist
|
|
62
|
+
* below the reading position, so shrinking the offset by the same amount is what
|
|
63
|
+
* keeps the screen still — and it is self-correcting when the source of the
|
|
64
|
+
* delta was an upper-bound height estimate that a later measurement brings down
|
|
65
|
+
* (`estimateEntryRows` is deliberately an upper bound). An unsigned rule
|
|
66
|
+
* over-counts once and never finds its way back.
|
|
67
|
+
*/
|
|
68
|
+
export function reduceFollow(input) {
|
|
69
|
+
const { offset, overflowLines, tailDelta, hold } = input;
|
|
70
|
+
// Pinned and not holding: anchoring is free — `offset` stays 0 and the
|
|
71
|
+
// existing arithmetic keeps showing the tail. `hold` LIFTS the precondition
|
|
72
|
+
// because a drag that starts while pinned must still freeze the rows under the
|
|
73
|
+
// pointer (S3), and freezing means the pin becomes an anchor for the duration
|
|
74
|
+
// of the drag (T-20).
|
|
75
|
+
if (offset === 0 && !hold)
|
|
76
|
+
return { offset, newLinesWhilePaused: 0 };
|
|
77
|
+
if (tailDelta === 0)
|
|
78
|
+
return { offset, newLinesWhilePaused: input.newLinesWhilePaused };
|
|
79
|
+
const next = clampScroll(offset + tailDelta, overflowLines);
|
|
80
|
+
return {
|
|
81
|
+
offset: next,
|
|
82
|
+
// Only real arrivals arm the resume timer, and reaching the bottom by ANY
|
|
83
|
+
// route resets the count (Rule B condition 2 / G6). Expressing the reset
|
|
84
|
+
// here is what keeps it one rule rather than two.
|
|
85
|
+
newLinesWhilePaused: next === 0 ? 0 : Math.max(0, input.newLinesWhilePaused + tailDelta),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Rule B's three conditions, and they are three rather than one on purpose:
|
|
90
|
+
*
|
|
91
|
+
* 1. `offset > 0` — there is somewhere to return FROM;
|
|
92
|
+
* 2. `newLines > 0` — something arrived that is worth returning TO. THIS IS
|
|
93
|
+
* G6: a user reading a FINISHED transcript is never yanked to the bottom,
|
|
94
|
+
* which is the new bug that a timing-only rule would ship wearing the fix's
|
|
95
|
+
* clothes (D-3);
|
|
96
|
+
* 3. `!hold` — no selection drag is in progress (§4.4).
|
|
97
|
+
*
|
|
98
|
+
* AC-9 rides on the first line: with `scrollResumeMs: 0` this returns `false`
|
|
99
|
+
* before anything else is considered, so no timer is ever created.
|
|
100
|
+
*/
|
|
101
|
+
export function shouldArmResume(input) {
|
|
102
|
+
if (!Number.isFinite(input.resumeMs) || input.resumeMs <= 0)
|
|
103
|
+
return false;
|
|
104
|
+
if (input.offset <= 0)
|
|
105
|
+
return false;
|
|
106
|
+
if (input.newLinesWhilePaused <= 0)
|
|
107
|
+
return false;
|
|
108
|
+
if (input.hold)
|
|
109
|
+
return false;
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
export function emptyTailState() {
|
|
113
|
+
return { rows: 0, lastId: null, lastHeight: 0 };
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Advance the tail counter by one frame.
|
|
117
|
+
*
|
|
118
|
+
* `ids` are the entries the transcript is drawing, in order; `heightOf(index)`
|
|
119
|
+
* is the transcript's own per-entry height table (measured where it exists, an
|
|
120
|
+
* upper-bound estimate where it does not). NO GEOMETRY IS READ HERE, which is
|
|
121
|
+
* what breaks P0-1's loop by construction.
|
|
122
|
+
*
|
|
123
|
+
* RE-ANCHORING CONTRIBUTES ZERO (R-11). When the scroll horizon drops the entry
|
|
124
|
+
* this counter was anchored on, the anchor is simply moved to the new last entry
|
|
125
|
+
* and the frame contributes a delta of 0. Losing one frame of anchoring is a row
|
|
126
|
+
* of drift at worst; treating a FRONT-DROP as tail motion is a visible jump, and
|
|
127
|
+
* an unbounded one.
|
|
128
|
+
*/
|
|
129
|
+
export function advanceTailRows(prev, ids, heightOf) {
|
|
130
|
+
if (ids.length === 0)
|
|
131
|
+
return { rows: prev.rows, lastId: null, lastHeight: 0 };
|
|
132
|
+
const lastIndex = ids.length - 1;
|
|
133
|
+
const lastId = ids[lastIndex];
|
|
134
|
+
const lastHeight = heightOf(lastIndex);
|
|
135
|
+
if (prev.lastId === null) {
|
|
136
|
+
// First frame: there is no "before", so there is no delta to attribute.
|
|
137
|
+
return { rows: prev.rows, lastId, lastHeight };
|
|
138
|
+
}
|
|
139
|
+
const anchor = ids.lastIndexOf(prev.lastId);
|
|
140
|
+
if (anchor === -1) {
|
|
141
|
+
// The anchor is gone (horizon front-drop, `/clear`, `/reset`, `/resume`).
|
|
142
|
+
return { rows: prev.rows, lastId, lastHeight };
|
|
143
|
+
}
|
|
144
|
+
// The anchor entry may have grown in place (a streaming tail entry), and
|
|
145
|
+
// everything after it is new. Both terms are signed.
|
|
146
|
+
let delta = heightOf(anchor) - prev.lastHeight;
|
|
147
|
+
for (let i = anchor + 1; i <= lastIndex; i += 1)
|
|
148
|
+
delta += heightOf(i);
|
|
149
|
+
return { rows: prev.rows + delta, lastId, lastHeight };
|
|
150
|
+
}
|
|
@@ -20,6 +20,17 @@
|
|
|
20
20
|
* `overflowLines`, while `offset` — and hence what the user is looking at — is
|
|
21
21
|
* unchanged. No scroll-anchoring compensation pass is required. Do NOT
|
|
22
22
|
* "simplify" `offset` to count from the top.
|
|
23
|
+
*
|
|
24
|
+
* V-4 AS AMENDED BY tui-selection-and-scroll-follow (§4.3.1a). There is now
|
|
25
|
+
* exactly ONE compensation pass — `follow-state.ts`'s Rule A — and it is
|
|
26
|
+
* deliberately restricted to rows appended AT THE TAIL, published by
|
|
27
|
+
* `Transcript.tsx` from the height table below. Everything the paragraph above
|
|
28
|
+
* describes still self-compensates and is still not compensated for again.
|
|
29
|
+
* The restriction is the whole of V-4's survival: an offset that reacted to
|
|
30
|
+
* TOTAL measured height would turn every above-the-viewport correction into a
|
|
31
|
+
* jump, would read a horizon front-drop as new output — and, because
|
|
32
|
+
* `selectWindow` takes `offset` as an INPUT, would close a loop from the offset
|
|
33
|
+
* through the mounted set and the measured heights back to the offset.
|
|
23
34
|
*/
|
|
24
35
|
import type { Entry } from '../../agent/reducer.js';
|
|
25
36
|
import type { DensityMode } from '../density.js';
|
|
@@ -20,9 +20,22 @@
|
|
|
20
20
|
* `overflowLines`, while `offset` — and hence what the user is looking at — is
|
|
21
21
|
* unchanged. No scroll-anchoring compensation pass is required. Do NOT
|
|
22
22
|
* "simplify" `offset` to count from the top.
|
|
23
|
+
*
|
|
24
|
+
* V-4 AS AMENDED BY tui-selection-and-scroll-follow (§4.3.1a). There is now
|
|
25
|
+
* exactly ONE compensation pass — `follow-state.ts`'s Rule A — and it is
|
|
26
|
+
* deliberately restricted to rows appended AT THE TAIL, published by
|
|
27
|
+
* `Transcript.tsx` from the height table below. Everything the paragraph above
|
|
28
|
+
* describes still self-compensates and is still not compensated for again.
|
|
29
|
+
* The restriction is the whole of V-4's survival: an offset that reacted to
|
|
30
|
+
* TOTAL measured height would turn every above-the-viewport correction into a
|
|
31
|
+
* jump, would read a horizon front-drop as new output — and, because
|
|
32
|
+
* `selectWindow` takes `offset` as an INPUT, would close a loop from the offset
|
|
33
|
+
* through the mounted set and the measured heights back to the offset.
|
|
23
34
|
*/
|
|
24
35
|
import { FAST_LIMITS } from '../../fast/limits.js';
|
|
36
|
+
import { COMPACTION_LIMITS } from '../../compaction/limits.js';
|
|
25
37
|
import { GUTTER_WIDTH } from './Gutter.js';
|
|
38
|
+
import { USER_ENTRY_MAX_ROWS } from '../composer-limits.js';
|
|
26
39
|
export const VIRTUAL_LIMITS = {
|
|
27
40
|
/**
|
|
28
41
|
* Entries kept mounted on each side of the visible band.
|
|
@@ -101,6 +114,32 @@ export function entryRevision(entry) {
|
|
|
101
114
|
*/
|
|
102
115
|
case 'fast':
|
|
103
116
|
return `f${entry.status}.${entry.live ? 1 : 0}.${entry.text?.length ?? 0}.${entry.detail?.length ?? 0}`;
|
|
117
|
+
/**
|
|
118
|
+
* `live`, the SUMMARY LENGTH and `mode`, because all three change the rows the
|
|
119
|
+
* card draws: a live card renders a headline and no body, a settled one
|
|
120
|
+
* renders the summary (whose length decides how many rows it wraps to), and
|
|
121
|
+
* `mode` selects between the "summarized N messages" and "dropped N WITHOUT a
|
|
122
|
+
* summary" detail lines. `durationMs` is inside the `wrap="truncate"`
|
|
123
|
+
* headline, so it can never change the height.
|
|
124
|
+
*/
|
|
125
|
+
case 'compaction':
|
|
126
|
+
return `c${entry.live ? 1 : 0}.${entry.summary?.length ?? 0}.${entry.mode}.${entry.applied ? 1 : 0}`;
|
|
127
|
+
/**
|
|
128
|
+
* `rowsSeen`, AND NEVER `rows.length` (background-service-supervision P0-3).
|
|
129
|
+
*
|
|
130
|
+
* The service tail is a FIXED-SIZE RING: it evicts its oldest row while
|
|
131
|
+
* appending a new one, so the joined length is unchanged while the content is
|
|
132
|
+
* not. That is verbatim the NON-APPEND mutation I-L3-1 above says must extend
|
|
133
|
+
* the revision, and it is the same trap — and the same fix — as the `tool`
|
|
134
|
+
* branch's `liveSeq` two cases up. A length term would go on matching while
|
|
135
|
+
* the card changed underneath it, and the card would freeze at a stale height
|
|
136
|
+
* AND a stale rendered subtree with nothing anywhere reporting it.
|
|
137
|
+
*
|
|
138
|
+
* `status` and the presence of a URL are in because both change the rows the
|
|
139
|
+
* card draws; `exitCode` is in because the terminal row names it.
|
|
140
|
+
*/
|
|
141
|
+
case 'service':
|
|
142
|
+
return `v${entry.status}.${entry.rowsSeen}.${entry.url ? 1 : 0}.${entry.exitCode ?? -1}.${entry.terminal ? 1 : 0}`;
|
|
104
143
|
default:
|
|
105
144
|
return 'x';
|
|
106
145
|
}
|
|
@@ -142,6 +181,10 @@ const TOOL_COLLAPSED_LINES = 8;
|
|
|
142
181
|
const DIFF_COLLAPSED_LINES = 12;
|
|
143
182
|
/** Live tail rows in `ToolCard`; duplicated as a NUMBER, for the same reason. */
|
|
144
183
|
const TOOL_LIVE_TAIL_ROWS = 8;
|
|
184
|
+
/** Tail rows a COLLAPSED `ServiceCard` draws; duplicated for the same reason. */
|
|
185
|
+
const SERVICE_COLLAPSED_TAIL_ROWS = 2;
|
|
186
|
+
/** Tail rows an EXPANDED `ServiceCard` draws; the snapshot cap (8). */
|
|
187
|
+
const SERVICE_EXPANDED_TAIL_ROWS = 8;
|
|
145
188
|
/**
|
|
146
189
|
* Pure UPPER-BOUND estimate of an entry's height, used until a real measurement
|
|
147
190
|
* exists.
|
|
@@ -159,7 +202,14 @@ export function estimateEntryRows(entry, cols, density, expanded, thinkingVisibl
|
|
|
159
202
|
const separation = density === 'comfortable' ? 1 : 0;
|
|
160
203
|
switch (entry.kind) {
|
|
161
204
|
case 'user':
|
|
162
|
-
|
|
205
|
+
// MIRRORS `UserEntry`'s RENDER CAP (+1 for the `... +N more lines` tail).
|
|
206
|
+
// THESE TWO NUMBERS ARE ONE NUMBER (I-13). Capping the renderer alone
|
|
207
|
+
// would leave this function believing a 5 000-line message is 5 000 rows
|
|
208
|
+
// tall -- and unlike an ordinary over-estimate that failure does NOT
|
|
209
|
+
// self-correct: this file's own note above records that "an entry
|
|
210
|
+
// estimated at 400 rows is exactly the one that never gets mounted", and
|
|
211
|
+
// an unmounted entry is never measured.
|
|
212
|
+
return (separation + Math.min(wrappedRows(entry.text, usable), USER_ENTRY_MAX_ROWS + 1));
|
|
163
213
|
case 'notice':
|
|
164
214
|
return separation + wrappedRows(entry.text, usable);
|
|
165
215
|
case 'assistant': {
|
|
@@ -243,6 +293,40 @@ export function estimateEntryRows(entry, cols, density, expanded, thinkingVisibl
|
|
|
243
293
|
: 1;
|
|
244
294
|
return separation + 1 + body;
|
|
245
295
|
}
|
|
296
|
+
// THE SECOND SWITCH, and adding the case to only one of the two yields a
|
|
297
|
+
// transcript whose scroll arithmetic disagrees with what it drew — drift
|
|
298
|
+
// rather than an error (C-9 / RV-13). Headline row, one detail row, then the
|
|
299
|
+
// summary clamped to `cardTextRows` (or unclamped when expanded) plus the
|
|
300
|
+
// `+N more lines` footer. An UPPER BOUND, which is what this function owes
|
|
301
|
+
// its caller.
|
|
302
|
+
case 'compaction': {
|
|
303
|
+
const lines = entry.summary ? wrappedRows(entry.summary, usable) : 0;
|
|
304
|
+
const body = expanded ? lines : Math.min(lines, COMPACTION_LIMITS.cardTextRows);
|
|
305
|
+
const footer = lines > body || (expanded && lines > COMPACTION_LIMITS.cardTextRows) ? 1 : 0;
|
|
306
|
+
return separation + 1 + 1 + body + footer;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* THE THIRD SWITCH, and adding the case to only one of this file's two
|
|
310
|
+
* yields a transcript whose scroll arithmetic disagrees with what it drew —
|
|
311
|
+
* drift rather than an error (P0-3 / RV-13). Both `default` branches swallow
|
|
312
|
+
* an unknown kind silently, and `virtual-window.test.ts` hand-enumerates
|
|
313
|
+
* kinds, so neither omission turns anything red on its own.
|
|
314
|
+
*
|
|
315
|
+
* A terminal record is exactly one row. A live card is the status row, the
|
|
316
|
+
* URL row when there is one, and the tail — counted, not wrapped, which is
|
|
317
|
+
* legitimate only because every row is `wrap="truncate"` AND clipped at
|
|
318
|
+
* `LIVE_ROW_MAX_CHARS` on the way into the ring, the same coupling the
|
|
319
|
+
* `tool` branch above records for itself.
|
|
320
|
+
*/
|
|
321
|
+
case 'service': {
|
|
322
|
+
if (entry.terminal)
|
|
323
|
+
return separation + 1;
|
|
324
|
+
const url = entry.url ? 1 : 0;
|
|
325
|
+
const tail = expanded
|
|
326
|
+
? Math.min(entry.rows.length, SERVICE_EXPANDED_TAIL_ROWS)
|
|
327
|
+
: Math.min(entry.rows.length, SERVICE_COLLAPSED_TAIL_ROWS);
|
|
328
|
+
return separation + 1 + url + tail;
|
|
329
|
+
}
|
|
246
330
|
default:
|
|
247
331
|
return separation + 1;
|
|
248
332
|
}
|
|
@@ -21,7 +21,14 @@ function keyRows(times) {
|
|
|
21
21
|
['Shift+Up / Shift+Down', 'Scroll the transcript a line (full-screen mode)'],
|
|
22
22
|
['Wheel', 'Scroll the transcript (or the open overlay)'],
|
|
23
23
|
['Shift+Wheel', 'Scroll the transcript a page'],
|
|
24
|
+
// Drag-select is the one affordance here a user cannot discover by pressing
|
|
25
|
+
// a key, so it has to be named somewhere they can look it up. The one-shot
|
|
26
|
+
// startup notice is the other place, and it scrolls away.
|
|
27
|
+
['Drag (left button)', 'Select text on screen; releasing copies it'],
|
|
24
28
|
['Up / Down', 'Prompt history (empty input)'],
|
|
29
|
+
// Pasting is the one affordance here with no key of its own, so the only
|
|
30
|
+
// place a user can learn what happened to their 218 lines is this row.
|
|
31
|
+
['Paste', 'Inserted as-is up to 6 lines; larger becomes [Pasted text #N] (--no-paste)'],
|
|
25
32
|
];
|
|
26
33
|
}
|
|
27
34
|
const COMMANDS = [
|
|
@@ -44,7 +51,8 @@ const COMMANDS = [
|
|
|
44
51
|
['/cwd [dir]', 'Show or change the tool working directory'],
|
|
45
52
|
['/save [file]', 'Save the session to JSON'],
|
|
46
53
|
['/resume [file]', 'Load a saved session'],
|
|
47
|
-
['/copy', 'Copy the last answer to the clipboard'],
|
|
54
|
+
['/copy', 'Copy the last answer to the clipboard (OSC 52, so it works over ssh)'],
|
|
55
|
+
['/mouse [on|off]', 'Release the mouse to your terminal, or take it back'],
|
|
48
56
|
['/exit', 'Exit'],
|
|
49
57
|
];
|
|
50
58
|
const SKILL_ROWS = [
|
|
@@ -27,6 +27,7 @@ import { Text, useInput } from 'ink';
|
|
|
27
27
|
import { pickGlyphs } from '../glyphs.js';
|
|
28
28
|
import { OverlayFrame } from '../layout/OverlayFrame.js';
|
|
29
29
|
import { wrapToRows } from '../layout/wrap-rows.js';
|
|
30
|
+
import { stripPasteFrames } from '../paste-frames.js';
|
|
30
31
|
/** The frame's border plus its one-column padding on each side. */
|
|
31
32
|
const FRAME_CHROME_COLS = 4;
|
|
32
33
|
export function PlanReviewOverlay({ plan, maxRows, cols, scrollOffset, onScrollClamp, theme, caps, onVerdict, }) {
|
|
@@ -55,8 +56,15 @@ export function PlanReviewOverlay({ plan, maxRows, cols, scrollOffset, onScrollC
|
|
|
55
56
|
setFeedback(feedback.slice(0, -1));
|
|
56
57
|
return;
|
|
57
58
|
}
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
// `stripPasteFrames`, NOT `input` (I-12 / P0-2). This is the THIRD consumer
|
|
60
|
+
// that appends `input` to a string; the design's table listed it as
|
|
61
|
+
// "key-driven only, inert", which was true of the version that had no
|
|
62
|
+
// free-text field. Revision feedback is exactly the kind of thing a user
|
|
63
|
+
// pastes — a failing test, a stack trace — and a framed paste appended
|
|
64
|
+
// verbatim would put NULs into text the model then reads.
|
|
65
|
+
if (input && !key.ctrl && !key.meta && !key.tab) {
|
|
66
|
+
setFeedback(feedback + stripPasteFrames(input));
|
|
67
|
+
}
|
|
60
68
|
return;
|
|
61
69
|
}
|
|
62
70
|
if (input === 'a' || input === 'A')
|
|
@@ -19,6 +19,7 @@ import { useState } from 'react';
|
|
|
19
19
|
import { Box, Text, useInput } from 'ink';
|
|
20
20
|
import { pickGlyphs } from '../glyphs.js';
|
|
21
21
|
import { OverlayFrame } from '../layout/OverlayFrame.js';
|
|
22
|
+
import { stripPasteFrames } from '../paste-frames.js';
|
|
22
23
|
/** Which option index each question starts on: the recommended one. */
|
|
23
24
|
function initialCursors(questions) {
|
|
24
25
|
return questions.map((q) => Math.max(0, q.options.findIndex((o) => o.recommended)));
|
|
@@ -121,8 +122,11 @@ export function QuestionOverlay({ questions, maxRows, cols, theme, caps, onSubmi
|
|
|
121
122
|
setDraft(draft.slice(0, -1));
|
|
122
123
|
return;
|
|
123
124
|
}
|
|
125
|
+
// `stripPasteFrames`, NOT `input` (I-12 / P0-2) -- the same broadcast
|
|
126
|
+
// hazard `SettingsScreen` documents at length: a framed paste appended
|
|
127
|
+
// verbatim stores NULs that render as nothing.
|
|
124
128
|
if (input && !key.ctrl && !key.meta && !key.tab)
|
|
125
|
-
setDraft(draft + input);
|
|
129
|
+
setDraft(draft + stripPasteFrames(input));
|
|
126
130
|
return;
|
|
127
131
|
}
|
|
128
132
|
// phase === 'choosing'
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import React from 'react';
|
|
11
11
|
import { type ThinkingLevel } from '@aragon-agent/core';
|
|
12
|
-
import { type FastConfig } from '../../config/schema.js';
|
|
12
|
+
import { type CompactionConfig, type FastConfig } from '../../config/schema.js';
|
|
13
13
|
import { type LogLevelName } from '../../logging/levels.js';
|
|
14
14
|
import type { Theme } from '../theme.js';
|
|
15
15
|
import type { TermCapabilities } from '../capabilities.js';
|
|
@@ -57,6 +57,33 @@ export interface SettingsValues {
|
|
|
57
57
|
fastReview: string;
|
|
58
58
|
/** The session review budget. `fastReview` is the PACE, this is the TOTAL. */
|
|
59
59
|
fastReviewBudget: string;
|
|
60
|
+
/**
|
|
61
|
+
* Context compaction (context-auto-compaction §4.5).
|
|
62
|
+
*
|
|
63
|
+
* THREE ROWS, NOT SIX. `onFailure` and `useFastTier` are file- and
|
|
64
|
+
* `/compact`-only on purpose: this screen is for the settings a user CHANGES,
|
|
65
|
+
* not for every setting that exists, and "what should it do if the summarizer
|
|
66
|
+
* 500s" is not a question a user reaches for with arrow keys.
|
|
67
|
+
*
|
|
68
|
+
* `string` rather than their config types because this screen edits TEXT, and
|
|
69
|
+
* a typo must change NOTHING rather than resolve to a default — the discipline
|
|
70
|
+
* `maxTokens` and `fastReview` already follow here.
|
|
71
|
+
*/
|
|
72
|
+
compactionEnabled: 'on' | 'off';
|
|
73
|
+
/** A percentage the user types, e.g. `90`. Parsed on save; a typo is ignored. */
|
|
74
|
+
compactionThreshold: string;
|
|
75
|
+
compactionKeepTurns: string;
|
|
76
|
+
/**
|
|
77
|
+
* The two hardening toggles (context-auto-compaction-hardening §4.6).
|
|
78
|
+
*
|
|
79
|
+
* FIVE ROWS NOW, NOT THREE, and these two earn their place where `onFailure`
|
|
80
|
+
* did not: each one names a THING THE SESSION WILL DO - spend money inside a
|
|
81
|
+
* fan-out, and write conversation content to disk - rather than a policy for a
|
|
82
|
+
* failure the user has never seen. Both are questions a user reaches for
|
|
83
|
+
* deliberately.
|
|
84
|
+
*/
|
|
85
|
+
compactionSubagents: 'on' | 'off';
|
|
86
|
+
compactionArchive: 'on' | 'off';
|
|
60
87
|
}
|
|
61
88
|
interface SettingsScreenProps {
|
|
62
89
|
initial: SettingsValues;
|
|
@@ -121,6 +148,25 @@ export declare function readFastSettings(values: Pick<SettingsValues, 'fastEnabl
|
|
|
121
148
|
* save writes the clamp's default over whatever the user had.
|
|
122
149
|
*/
|
|
123
150
|
export declare function fastSettingsFrom(fast: FastConfig): Pick<SettingsValues, 'fastEnabled' | 'fastModel' | 'fastProvider' | 'fastReview' | 'fastReviewBudget'>;
|
|
151
|
+
/**
|
|
152
|
+
* Read the three compaction rows back into a config patch.
|
|
153
|
+
*
|
|
154
|
+
* A TYPO CHANGES NOTHING — the discipline `readFastSettings` states one function
|
|
155
|
+
* up, and the reason is the same: a threshold that silently resolved to the
|
|
156
|
+
* default on a mistyped keystroke would be a setting the user believes they
|
|
157
|
+
* changed and did not. `parseThresholdInput` returns `null` for anything that is
|
|
158
|
+
* not a ratio or a percentage, and the key is then simply absent from the patch.
|
|
159
|
+
*/
|
|
160
|
+
export declare function readCompactionSettings(values: Pick<SettingsValues, 'compactionEnabled' | 'compactionThreshold' | 'compactionKeepTurns' | 'compactionSubagents' | 'compactionArchive'>): Partial<CompactionConfig>;
|
|
161
|
+
/**
|
|
162
|
+
* The inverse: seed the three rows from a resolved config.
|
|
163
|
+
*
|
|
164
|
+
* OMITTING A KEY HERE IS THE SILENT HALF OF ADDING A ROW, exactly as it is for
|
|
165
|
+
* the fast rows above: the row renders empty, `readCompactionSettings` sees an
|
|
166
|
+
* unparseable value, and an untouched save writes the clamp's default over
|
|
167
|
+
* whatever the user had.
|
|
168
|
+
*/
|
|
169
|
+
export declare function compactionSettingsFrom(compaction: CompactionConfig): Pick<SettingsValues, 'compactionEnabled' | 'compactionThreshold' | 'compactionKeepTurns' | 'compactionSubagents' | 'compactionArchive'>;
|
|
124
170
|
/**
|
|
125
171
|
* The cap this setting will ACTUALLY produce against the selected model.
|
|
126
172
|
*
|
|
@@ -11,13 +11,15 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
11
11
|
import { useState } from 'react';
|
|
12
12
|
import { Text, useInput } from 'ink';
|
|
13
13
|
import { DEFAULT_MAX_OUTPUT_TOKENS, resolveOutputTokens, } from '@aragon-agent/core';
|
|
14
|
-
import { ADAPTER_PROVIDERS, THINKING_LEVELS, maskSecret, parseMaxTokensInput, } from '../../config/schema.js';
|
|
14
|
+
import { ADAPTER_PROVIDERS, THINKING_LEVELS, maskSecret, parseMaxTokensInput, parseThresholdInput, } from '../../config/schema.js';
|
|
15
15
|
import { LOG_LEVEL_NAMES } from '../../logging/levels.js';
|
|
16
16
|
import { pickGlyphs } from '../glyphs.js';
|
|
17
17
|
import { OverlayFrame } from '../layout/OverlayFrame.js';
|
|
18
|
+
import { stripPasteFrames } from '../paste-frames.js';
|
|
18
19
|
const SHOW_THINKING_OPTIONS = ['off', 'on'];
|
|
19
20
|
const LIVE_OUTPUT_OPTIONS = ['off', 'on'];
|
|
20
21
|
const FAST_ENABLED_OPTIONS = ['off', 'on'];
|
|
22
|
+
const COMPACTION_ENABLED_OPTIONS = ['off', 'on'];
|
|
21
23
|
/**
|
|
22
24
|
* `(inherit)` IS THE EMPTY STRING, rendered as a word.
|
|
23
25
|
*
|
|
@@ -107,6 +109,48 @@ export function fastSettingsFrom(fast) {
|
|
|
107
109
|
fastReviewBudget: String(fast.reviewMaxPerSession),
|
|
108
110
|
};
|
|
109
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Read the three compaction rows back into a config patch.
|
|
114
|
+
*
|
|
115
|
+
* A TYPO CHANGES NOTHING — the discipline `readFastSettings` states one function
|
|
116
|
+
* up, and the reason is the same: a threshold that silently resolved to the
|
|
117
|
+
* default on a mistyped keystroke would be a setting the user believes they
|
|
118
|
+
* changed and did not. `parseThresholdInput` returns `null` for anything that is
|
|
119
|
+
* not a ratio or a percentage, and the key is then simply absent from the patch.
|
|
120
|
+
*/
|
|
121
|
+
export function readCompactionSettings(values) {
|
|
122
|
+
const threshold = parseThresholdInput(values.compactionThreshold);
|
|
123
|
+
const keep = values.compactionKeepTurns.trim();
|
|
124
|
+
const parsedKeep = /^\d+$/.test(keep) ? Number.parseInt(keep, 10) : Number.NaN;
|
|
125
|
+
return {
|
|
126
|
+
enabled: values.compactionEnabled === 'on',
|
|
127
|
+
...(threshold !== null ? { threshold } : {}),
|
|
128
|
+
...(Number.isFinite(parsedKeep) && parsedKeep > 0 ? { keepRecentTurns: parsedKeep } : {}),
|
|
129
|
+
// AN ENUM ROW CANNOT BE MISTYPED, so unlike the two text rows above these are
|
|
130
|
+
// written unconditionally.
|
|
131
|
+
subagents: values.compactionSubagents === 'on',
|
|
132
|
+
archive: values.compactionArchive === 'on',
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* The inverse: seed the three rows from a resolved config.
|
|
137
|
+
*
|
|
138
|
+
* OMITTING A KEY HERE IS THE SILENT HALF OF ADDING A ROW, exactly as it is for
|
|
139
|
+
* the fast rows above: the row renders empty, `readCompactionSettings` sees an
|
|
140
|
+
* unparseable value, and an untouched save writes the clamp's default over
|
|
141
|
+
* whatever the user had.
|
|
142
|
+
*/
|
|
143
|
+
export function compactionSettingsFrom(compaction) {
|
|
144
|
+
return {
|
|
145
|
+
compactionEnabled: compaction.enabled ? 'on' : 'off',
|
|
146
|
+
// A PERCENTAGE, because that is the unit the status bar shows and the unit a
|
|
147
|
+
// user thinks in. `parseThresholdInput` accepts it back.
|
|
148
|
+
compactionThreshold: String(Math.round(compaction.threshold * 100)),
|
|
149
|
+
compactionKeepTurns: String(compaction.keepRecentTurns),
|
|
150
|
+
compactionSubagents: compaction.subagents ? 'on' : 'off',
|
|
151
|
+
compactionArchive: compaction.archive ? 'on' : 'off',
|
|
152
|
+
};
|
|
153
|
+
}
|
|
110
154
|
const FIELDS = [
|
|
111
155
|
{ key: 'provider', label: 'Provider', kind: 'enum', options: ADAPTER_PROVIDERS },
|
|
112
156
|
{ key: 'model', label: 'Model', kind: 'text' },
|
|
@@ -146,6 +190,22 @@ const FIELDS = [
|
|
|
146
190
|
{ key: 'fastProvider', label: 'Fast provider', kind: 'enum', options: FAST_PROVIDER_OPTIONS },
|
|
147
191
|
{ key: 'fastReview', label: 'Fast review', kind: 'text' },
|
|
148
192
|
{ key: 'fastReviewBudget', label: 'Fast budget', kind: 'text' },
|
|
193
|
+
// Context compaction sits after the fast rows and still BEFORE `API key`, for
|
|
194
|
+
// the reason recorded above that row: the key is the one a user scrolls to
|
|
195
|
+
// deliberately, and burying it further would be worse than the ordering is
|
|
196
|
+
// better. Three rows, and every label is inside the 18-column budget
|
|
197
|
+
// `.padEnd(18)` enforces below.
|
|
198
|
+
//
|
|
199
|
+
// `Compact at` is a PERCENTAGE and carries no `(50-95)` hint, for the reason
|
|
200
|
+
// `Fast budget` records: no other bounded row carries its range either,
|
|
201
|
+
// `clampCompactionConfig` is the enforcement, and `/compact threshold` states
|
|
202
|
+
// the range in the one place a user meets it — when it refuses a bad one.
|
|
203
|
+
{ key: 'compactionEnabled', label: 'Auto-compaction', kind: 'enum', options: COMPACTION_ENABLED_OPTIONS },
|
|
204
|
+
{ key: 'compactionThreshold', label: 'Compact at %', kind: 'text' },
|
|
205
|
+
{ key: 'compactionKeepTurns', label: 'Keep turns', kind: 'text' },
|
|
206
|
+
// Both labels are inside the 18-column budget `.padEnd(18)` enforces below.
|
|
207
|
+
{ key: 'compactionSubagents', label: 'Compact children', kind: 'enum', options: COMPACTION_ENABLED_OPTIONS },
|
|
208
|
+
{ key: 'compactionArchive', label: 'Archive dropped', kind: 'enum', options: COMPACTION_ENABLED_OPTIONS },
|
|
149
209
|
{ key: 'apiKey', label: 'API key', kind: 'secret' },
|
|
150
210
|
{ key: 'logLevel', label: 'Log level', kind: 'enum', options: LOG_LEVEL_NAMES },
|
|
151
211
|
];
|
|
@@ -233,7 +293,19 @@ export function SettingsScreen({ initial, apiKeys, maxRows, cols, scrollOffset,
|
|
|
233
293
|
return;
|
|
234
294
|
}
|
|
235
295
|
if (input && !key.ctrl && !key.meta) {
|
|
236
|
-
|
|
296
|
+
// `stripPasteFrames`, NOT `input` (I-12 / P0-2). `useInput` is a BROADCAST,
|
|
297
|
+
// so this handler sees the NUL-framed paste the composer was sent. An API
|
|
298
|
+
// key is ~100 characters with no line break, which trips the filter's Tier
|
|
299
|
+
// 2 burst rule and gets framed -- and nothing downstream would remove the
|
|
300
|
+
// NULs: `trim()` does not treat U+0000 as whitespace, `maskDot` hides the
|
|
301
|
+
// damage, and the value is handed to `registerSecret()` and
|
|
302
|
+
// `controller.setApiKey()`. The user is told the settings were saved and
|
|
303
|
+
// every request afterwards fails to authenticate with nothing on screen to
|
|
304
|
+
// explain it. Pasting IS how a key enters this process.
|
|
305
|
+
const text = stripPasteFrames(input);
|
|
306
|
+
if (text.length === 0)
|
|
307
|
+
return;
|
|
308
|
+
setValues((prev) => ({ ...prev, [field.key]: String(prev[field.key]) + text }));
|
|
237
309
|
}
|
|
238
310
|
});
|
|
239
311
|
const displayValue = (f, selected) => {
|
package/dist/ui/palettes.d.ts
CHANGED
|
@@ -32,6 +32,22 @@ export interface Palette {
|
|
|
32
32
|
idleBorder: string;
|
|
33
33
|
hintFg: string;
|
|
34
34
|
logoShadow: string;
|
|
35
|
+
/**
|
|
36
|
+
* Drag-selection highlight (tui-selection-and-scroll-follow §4.4.4).
|
|
37
|
+
*
|
|
38
|
+
* A PAIR, not a background alone: the selected run has its inner SGR stripped
|
|
39
|
+
* before it is repainted, so whatever foreground the row had is gone by the
|
|
40
|
+
* time the highlight is applied and a background on its own would leave the
|
|
41
|
+
* text at the terminal's default colour over an arbitrary hue. The two are
|
|
42
|
+
* contrast-pinned against EACH OTHER in `palettes.test.ts` — the surface a
|
|
43
|
+
* selected glyph sits on is `selectionBg`, not `DARK_SURFACE`.
|
|
44
|
+
*
|
|
45
|
+
* Below ansi-256 these never reach the terminal at all: `highlight.ts` emits
|
|
46
|
+
* plain reverse video there, because `getTheme` has degraded them to chalk
|
|
47
|
+
* colour NAMES by then and a name is not an escape sequence.
|
|
48
|
+
*/
|
|
49
|
+
selectionBg: string;
|
|
50
|
+
selectionFg: string;
|
|
35
51
|
gradient: [string, string, string];
|
|
36
52
|
gauge: {
|
|
37
53
|
track: string;
|
package/dist/ui/palettes.js
CHANGED
|
@@ -39,6 +39,8 @@ export const WARM = {
|
|
|
39
39
|
idleBorder: '#4a423b',
|
|
40
40
|
hintFg: '#9c9086',
|
|
41
41
|
logoShadow: '#4a423b',
|
|
42
|
+
selectionBg: '#e0b088',
|
|
43
|
+
selectionFg: '#1c1917',
|
|
42
44
|
gradient: ['#d08c60', '#c9a227', '#b0916a'],
|
|
43
45
|
gauge: { track: '#4a423b', low: '#7fb08a', mid: '#c9a227', high: '#cf6b5c' },
|
|
44
46
|
diff: { add: '#7fb08a', remove: '#cf6b5c', meta: '#d08c60', context: '#a2968a' },
|
|
@@ -66,6 +68,8 @@ export const COOL = {
|
|
|
66
68
|
idleBorder: '#3b4261',
|
|
67
69
|
hintFg: '#565f89',
|
|
68
70
|
logoShadow: '#3b4261',
|
|
71
|
+
selectionBg: '#7aa2f7',
|
|
72
|
+
selectionFg: '#1a1b26',
|
|
69
73
|
gradient: ['#7aa2f7', '#bb9af7', '#7dcfff'],
|
|
70
74
|
gauge: { track: '#3b4261', low: '#9ece6a', mid: '#e0af68', high: '#f7768e' },
|
|
71
75
|
diff: { add: '#9ece6a', remove: '#f7768e', meta: '#7aa2f7', context: '#7a86b8' },
|
|
@@ -92,6 +96,8 @@ export const LIGHT = {
|
|
|
92
96
|
idleBorder: '#c8cdd6',
|
|
93
97
|
hintFg: '#6b7280',
|
|
94
98
|
logoShadow: '#c8cdd6',
|
|
99
|
+
selectionBg: '#2959aa',
|
|
100
|
+
selectionFg: '#ffffff',
|
|
95
101
|
gradient: ['#2959aa', '#8c4bc9', '#0f7490'],
|
|
96
102
|
gauge: { track: '#c8cdd6', low: '#2e7d32', mid: '#b5730f', high: '#c62828' },
|
|
97
103
|
diff: { add: '#2e7d32', remove: '#c62828', meta: '#2959aa', context: '#6b7280' },
|