@aragon-agent/cli 0.6.4 → 0.6.8
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 +37 -0
- package/README.md +163 -37
- package/dist/agent/controller.d.ts +49 -8
- package/dist/agent/controller.js +160 -48
- package/dist/agent/headless.d.ts +2 -1
- package/dist/agent/prompt-options.d.ts +15 -0
- package/dist/agent/prompt-options.js +1 -0
- package/dist/agent/reducer.d.ts +33 -19
- package/dist/agent/reducer.js +62 -71
- package/dist/agent/usage.d.ts +34 -0
- package/dist/agent/usage.js +43 -0
- package/dist/cli.js +131 -54
- package/dist/commands/builtins.js +12 -1
- package/dist/commands/registry.d.ts +2 -3
- package/dist/compaction/compactor.d.ts +60 -24
- package/dist/compaction/compactor.js +89 -61
- package/dist/compaction/context-command.d.ts +33 -0
- package/dist/compaction/context-command.js +166 -0
- package/dist/compaction/meter.d.ts +251 -0
- package/dist/compaction/meter.js +428 -0
- package/dist/compaction/pressure.d.ts +6 -0
- package/dist/compaction/pressure.js +4 -0
- package/dist/compaction/types.d.ts +41 -0
- package/dist/compaction/wiring.d.ts +47 -0
- package/dist/compaction/wiring.js +88 -27
- package/dist/config/cli-commands.js +5 -0
- package/dist/config/env.js +15 -1
- package/dist/config/load.d.ts +17 -0
- package/dist/config/load.js +45 -1
- package/dist/config/schema.d.ts +44 -0
- package/dist/config/schema.js +36 -0
- package/dist/config/store.js +27 -1
- package/dist/exec/events.d.ts +138 -1
- package/dist/exec/events.js +42 -0
- package/dist/exec/index.js +43 -6
- package/dist/exec/progress-reporter.d.ts +23 -0
- package/dist/exec/progress-reporter.js +88 -0
- package/dist/exec/runner.d.ts +34 -0
- package/dist/exec/runner.js +137 -25
- package/dist/fast/reviewer.d.ts +19 -1
- package/dist/fast/reviewer.js +22 -0
- package/dist/fast/types.d.ts +15 -0
- package/dist/fast/wiring.d.ts +10 -1
- package/dist/fast/wiring.js +18 -1
- package/dist/input/interrupt-gesture.d.ts +18 -0
- package/dist/input/interrupt-gesture.js +19 -0
- package/dist/input/pointer-router.d.ts +11 -0
- package/dist/input/pointer-router.js +14 -0
- package/dist/todo/limits.d.ts +4 -17
- package/dist/todo/limits.js +4 -17
- package/dist/todo/panel-rows.d.ts +8 -6
- package/dist/todo/panel-rows.js +19 -6
- package/dist/todo/store.d.ts +8 -13
- package/dist/todo/store.js +13 -14
- package/dist/todo/types.d.ts +2 -1
- package/dist/ui/App.d.ts +2 -0
- package/dist/ui/App.js +233 -183
- package/dist/ui/AutocompletePopup.d.ts +3 -1
- package/dist/ui/AutocompletePopup.js +9 -11
- package/dist/ui/BottomStatusRow.d.ts +1 -1
- package/dist/ui/Composer.d.ts +13 -5
- package/dist/ui/Composer.js +7 -11
- package/dist/ui/PromptCaret.d.ts +11 -0
- package/dist/ui/PromptCaret.js +31 -0
- package/dist/ui/PromptInput.d.ts +12 -6
- package/dist/ui/PromptInput.js +50 -16
- package/dist/ui/StatusBar.d.ts +22 -15
- package/dist/ui/StatusBar.js +61 -16
- package/dist/ui/TeamPanel.d.ts +3 -1
- package/dist/ui/TeamPanel.js +13 -9
- package/dist/ui/TodoPanel.d.ts +5 -5
- package/dist/ui/TodoPanel.js +56 -24
- package/dist/ui/Transcript.js +1 -0
- package/dist/ui/UpdateLine.d.ts +1 -1
- package/dist/ui/capabilities.js +5 -5
- package/dist/ui/composer-rows.js +50 -9
- package/dist/ui/exit-snapshot.d.ts +9 -0
- package/dist/ui/frame-differ.js +2 -19
- package/dist/ui/frame-observer.d.ts +17 -0
- package/dist/ui/frame-observer.js +81 -0
- package/dist/ui/frame-parser.d.ts +7 -0
- package/dist/ui/frame-parser.js +18 -0
- package/dist/ui/layout/AppShell.d.ts +7 -52
- package/dist/ui/layout/AppShell.js +2 -2
- package/dist/ui/layout/ScrollIndicator.d.ts +4 -3
- package/dist/ui/layout/ScrollIndicator.js +4 -4
- package/dist/ui/layout/ScrollViewport.d.ts +13 -66
- package/dist/ui/layout/ScrollViewport.js +183 -185
- package/dist/ui/layout/autocomplete.d.ts +18 -0
- package/dist/ui/layout/autocomplete.js +26 -0
- package/dist/ui/layout/budget.d.ts +3 -51
- package/dist/ui/layout/budget.js +4 -53
- package/dist/ui/layout/document-layout.d.ts +13 -0
- package/dist/ui/layout/document-layout.js +11 -0
- package/dist/ui/layout/follow-state.d.ts +1 -0
- package/dist/ui/layout/follow-state.js +1 -3
- package/dist/ui/layout/rail.d.ts +4 -53
- package/dist/ui/layout/rail.js +10 -62
- package/dist/ui/layout/scroll-indicator.d.ts +1 -1
- package/dist/ui/layout/scroll-indicator.js +3 -2
- package/dist/ui/layout/scrollbar-geometry.d.ts +17 -0
- package/dist/ui/layout/scrollbar-geometry.js +13 -0
- package/dist/ui/layout/team-panel.d.ts +18 -0
- package/dist/ui/layout/team-panel.js +22 -0
- package/dist/ui/layout/todo-layout.d.ts +25 -0
- package/dist/ui/layout/todo-layout.js +28 -0
- package/dist/ui/layout/viewport-geometry.d.ts +1 -0
- package/dist/ui/layout/viewport-geometry.js +1 -0
- package/dist/ui/layout/virtual-window.d.ts +1 -0
- package/dist/ui/layout/virtual-window.js +1 -1
- package/dist/ui/overlays/HelpOverlay.js +5 -2
- package/dist/ui/screen.d.ts +1 -1
- package/dist/ui/screen.js +4 -6
- package/dist/ui/scrollbar-controller.d.ts +30 -0
- package/dist/ui/scrollbar-controller.js +124 -0
- package/dist/ui/transcript-text.js +6 -2
- package/dist/ui/use-wheel-routing.d.ts +2 -0
- package/dist/ui/use-wheel-routing.js +6 -0
- package/package.json +2 -2
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import React from 'react';
|
|
7
7
|
import type { Theme } from './theme.js';
|
|
8
8
|
import type { TermCapabilities } from './capabilities.js';
|
|
9
|
+
import { type AutocompleteLayout } from './layout/autocomplete.js';
|
|
9
10
|
export interface Suggestion {
|
|
10
11
|
label: string;
|
|
11
12
|
hint?: string;
|
|
@@ -16,6 +17,7 @@ interface AutocompletePopupProps {
|
|
|
16
17
|
theme: Theme;
|
|
17
18
|
caps: TermCapabilities;
|
|
18
19
|
maxRows?: number;
|
|
20
|
+
layout?: AutocompleteLayout;
|
|
19
21
|
}
|
|
20
|
-
export declare function AutocompletePopup({ items, selected, theme, caps, maxRows, }: AutocompletePopupProps): React.ReactElement | null;
|
|
22
|
+
export declare function AutocompletePopup({ items, selected, theme, caps, maxRows, layout, }: AutocompletePopupProps): React.ReactElement | null;
|
|
21
23
|
export {};
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
3
|
import { pickGlyphs } from './glyphs.js';
|
|
4
|
-
|
|
5
|
-
export function AutocompletePopup({ items, selected, theme, caps, maxRows
|
|
6
|
-
|
|
4
|
+
import { buildAutocompleteLayout } from './layout/autocomplete.js';
|
|
5
|
+
export function AutocompletePopup({ items, selected, theme, caps, maxRows, layout, }) {
|
|
6
|
+
const projection = layout ?? buildAutocompleteLayout({ itemCount: items.length, selected, maxRows });
|
|
7
|
+
if (projection.rowCount === 0)
|
|
7
8
|
return null;
|
|
8
9
|
const glyphs = pickGlyphs(caps);
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
const visible = items.slice(windowStart, windowStart + maxRows);
|
|
13
|
-
const moreBelow = items.length - (windowStart + visible.length);
|
|
14
|
-
return (_jsxs(Box, { flexDirection: "column", borderStyle: glyphs.boxStyle, borderColor: theme.border, paddingX: 1, children: [visible.map((item, i) => {
|
|
10
|
+
const { start: windowStart, count, moreBelow, showMore } = projection;
|
|
11
|
+
const visible = items.slice(windowStart, windowStart + count);
|
|
12
|
+
return (_jsxs(Box, { flexDirection: "column", flexShrink: 0, borderStyle: glyphs.boxStyle, borderColor: theme.border, paddingX: 1, children: [visible.map((item, i) => {
|
|
15
13
|
const idx = windowStart + i;
|
|
16
14
|
const active = idx === selected;
|
|
17
|
-
return (_jsxs(Text, { inverse: active, children: [_jsx(Text, { color: active ? undefined : theme.primary, children: item.label }), item.hint ? _jsxs(Text, { color: theme.muted, children: [" ", item.hint] }) : null] },
|
|
18
|
-
}),
|
|
15
|
+
return (_jsxs(Text, { inverse: active, wrap: "truncate", children: [_jsx(Text, { color: active ? undefined : theme.primary, children: item.label.replace(/\s+/g, ' ') }), item.hint ? _jsxs(Text, { color: theme.muted, children: [" ", item.hint.replace(/\s+/g, ' ')] }) : null] }, idx));
|
|
16
|
+
}), showMore && _jsxs(Text, { wrap: "truncate", color: theme.muted, children: [" +", moreBelow, " more"] })] }));
|
|
19
17
|
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* box is its only `flexShrink={1}` child — so an extra chrome row does not make
|
|
12
12
|
* the frame taller, Yoga takes the row out of the TRANSCRIPT. Meanwhile
|
|
13
13
|
* `viewportRows()` keeps returning the old number to `ScrollViewport`,
|
|
14
|
-
* `selectWindow`'s spacers, `overlayMaxRows`, `popupMaxRows` and `
|
|
14
|
+
* `selectWindow`'s spacers, `overlayMaxRows`, `popupMaxRows` and `buildTodoRailLayout`.
|
|
15
15
|
* The transcript would draw one row shorter than every consumer believes, ONLY
|
|
16
16
|
* WHILE A RUN IS IN FLIGHT, so the layout would shift on submit and shift back on
|
|
17
17
|
* `agent_end`.
|
package/dist/ui/Composer.d.ts
CHANGED
|
@@ -9,20 +9,26 @@
|
|
|
9
9
|
* "the input is no longer empty", and routing that through App would re-render
|
|
10
10
|
* the transcript on the first keystroke of every message.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* them 31 columns wide, were both redundant and the reason the status bar had a
|
|
15
|
-
* `cols >= 110` breakpoint.
|
|
12
|
+
* The fixed status row retains emergency and history-navigation hints while
|
|
13
|
+
* this footer scrolls outside the viewport. Editor state stays mounted.
|
|
16
14
|
*/
|
|
17
15
|
import React from 'react';
|
|
16
|
+
import { type DOMElement } from 'ink';
|
|
18
17
|
import type { Theme } from './theme.js';
|
|
19
18
|
import type { TermCapabilities } from './capabilities.js';
|
|
20
19
|
import { pickGlyphs } from './glyphs.js';
|
|
21
20
|
import { type CommandOption } from './PromptInput.js';
|
|
22
21
|
import { type AgentMode } from '../agent/agent-mode.js';
|
|
23
22
|
export interface ComposerProps {
|
|
23
|
+
cols?: number;
|
|
24
|
+
cursorVisible?: boolean;
|
|
25
|
+
onInteraction?: () => void;
|
|
26
|
+
measureRef?: React.RefObject<DOMElement>;
|
|
24
27
|
/** False while an overlay owns the keyboard (the input is visually blurred). */
|
|
25
28
|
isActive: boolean;
|
|
29
|
+
reducedMotion?: boolean;
|
|
30
|
+
onEscape?: () => void;
|
|
31
|
+
onEscapeDismiss?: () => void;
|
|
26
32
|
running: boolean;
|
|
27
33
|
history: string[];
|
|
28
34
|
commands: CommandOption[];
|
|
@@ -49,6 +55,8 @@ export interface ComposerProps {
|
|
|
49
55
|
*/
|
|
50
56
|
modeToggleKey?: string;
|
|
51
57
|
popupMaxRows?: number;
|
|
58
|
+
popupMaxHeight?: number;
|
|
59
|
+
onPopupRowsChange?: (rows: number) => void;
|
|
52
60
|
/**
|
|
53
61
|
* Rows of newer output below the viewport, forwarded to the input row's chip
|
|
54
62
|
* (tui-selection-and-scroll-follow §4.2 / G7).
|
|
@@ -117,5 +125,5 @@ declare function hintText(opts: {
|
|
|
117
125
|
* than fishing the string out of a rendered frame.
|
|
118
126
|
*/
|
|
119
127
|
export declare const hintTextForTest: typeof hintText;
|
|
120
|
-
export declare function Composer({ isActive, running, history, commands, cwd, showHint, submitCount, hintsEnabled, agentMode, services, modeToggleKey, popupMaxRows, scrolledLines, onDraftRows, onNotice, theme, caps, onSubmit, onHelp, }: ComposerProps): React.ReactElement;
|
|
128
|
+
export declare function Composer({ cols, cursorVisible, onInteraction, measureRef, isActive, reducedMotion, onEscape, onEscapeDismiss, running, history, commands, cwd, showHint, submitCount, hintsEnabled, agentMode, services, modeToggleKey, popupMaxRows, popupMaxHeight, onPopupRowsChange, scrolledLines, onDraftRows, onNotice, theme, caps, onSubmit, onHelp, }: ComposerProps): React.ReactElement;
|
|
121
129
|
export {};
|
package/dist/ui/Composer.js
CHANGED
|
@@ -10,10 +10,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
10
10
|
* "the input is no longer empty", and routing that through App would re-render
|
|
11
11
|
* the transcript on the first keystroke of every message.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* them 31 columns wide, were both redundant and the reason the status bar had a
|
|
16
|
-
* `cols >= 110` breakpoint.
|
|
13
|
+
* The fixed status row retains emergency and history-navigation hints while
|
|
14
|
+
* this footer scrolls outside the viewport. Editor state stays mounted.
|
|
17
15
|
*/
|
|
18
16
|
import { useCallback, useState } from 'react';
|
|
19
17
|
import { Box, Text } from 'ink';
|
|
@@ -46,15 +44,13 @@ function hintText(opts) {
|
|
|
46
44
|
if (services > 0) {
|
|
47
45
|
return [
|
|
48
46
|
`${glyphs.enterKey} steer`,
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
// is left exactly where the reported screenshot left them.
|
|
52
|
-
`esc abort${glyphs.times}2 force`,
|
|
47
|
+
// Confirmation is named even when service controls also need space.
|
|
48
|
+
`esc${glyphs.times}2 interrupt`,
|
|
53
49
|
`ctrl+c stop ${services}`,
|
|
54
50
|
`ctrl+c${glyphs.times}2 exit`,
|
|
55
51
|
].join(dot);
|
|
56
52
|
}
|
|
57
|
-
return [`${glyphs.enterKey} steer`,
|
|
53
|
+
return [`${glyphs.enterKey} steer`, `esc${glyphs.times}2 interrupt`, `ctrl+c${glyphs.times}2 exit`].join(dot);
|
|
58
54
|
}
|
|
59
55
|
// The hint names the DESTINATION, not the current state — the correct label
|
|
60
56
|
// for a toggle affordance, and the reason the mode word here is the OPPOSITE
|
|
@@ -90,7 +86,7 @@ function hintText(opts) {
|
|
|
90
86
|
* than fishing the string out of a rendered frame.
|
|
91
87
|
*/
|
|
92
88
|
export const hintTextForTest = hintText;
|
|
93
|
-
export function Composer({ isActive, running, history, commands, cwd, showHint, submitCount, hintsEnabled, agentMode, services = 0, modeToggleKey = MODE_TOGGLE_KEYS.primary, popupMaxRows, scrolledLines = 0, onDraftRows, onNotice, theme, caps, onSubmit, onHelp, }) {
|
|
89
|
+
export function Composer({ cols, cursorVisible, onInteraction, measureRef, isActive, reducedMotion, onEscape, onEscapeDismiss, running, history, commands, cwd, showHint, submitCount, hintsEnabled, agentMode, services = 0, modeToggleKey = MODE_TOGGLE_KEYS.primary, popupMaxRows, popupMaxHeight, onPopupRowsChange, scrolledLines = 0, onDraftRows, onNotice, theme, caps, onSubmit, onHelp, }) {
|
|
94
90
|
const [hasDraft, setHasDraft] = useState(false);
|
|
95
91
|
const onDraftChange = useCallback((next) => {
|
|
96
92
|
setHasDraft(next.hasDraft);
|
|
@@ -110,7 +106,7 @@ export function Composer({ isActive, running, history, commands, cwd, showHint,
|
|
|
110
106
|
: theme.idleBorder ?? theme.border;
|
|
111
107
|
const glyphs = pickGlyphs(caps);
|
|
112
108
|
const visible = showHint && hintsEnabled;
|
|
113
|
-
return (_jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [_jsx(PromptInput, { isActive: isActive, running: running, history: history, commands: commands, cwd: cwd, theme: theme, caps: caps, popupMaxRows: popupMaxRows, onSubmit: onSubmit, onHelp: onHelp, agentMode: agentMode, bordered: { color: borderColor }, onDraftChange: onDraftChange, onNotice: onNotice, scrolledLines: scrolledLines }), visible && (
|
|
109
|
+
return (_jsxs(Box, { ref: measureRef, flexDirection: "column", flexShrink: 0, children: [_jsx(PromptInput, { cols: cols, cursorVisible: cursorVisible, onInteraction: onInteraction, isActive: isActive, reducedMotion: reducedMotion, onEscape: onEscape, onEscapeDismiss: onEscapeDismiss, running: running, history: history, commands: commands, cwd: cwd, theme: theme, caps: caps, popupMaxRows: popupMaxRows, popupMaxHeight: popupMaxHeight, onPopupRowsChange: onPopupRowsChange, onSubmit: onSubmit, onHelp: onHelp, agentMode: agentMode, bordered: { color: borderColor }, onDraftChange: onDraftChange, onNotice: onNotice, scrolledLines: scrolledLines }), visible && (
|
|
114
110
|
// The chip rides the EXISTING hint row rather than adding one, so it
|
|
115
111
|
// costs zero rows on a 24-row terminal.
|
|
116
112
|
_jsxs(Box, { flexDirection: "row", flexShrink: 0, children: [_jsx(Text, { children: ' ' }), _jsx(ModeChip, { mode: agentMode, theme: theme, caps: caps }), _jsx(Text, { wrap: "truncate", color: theme.hintFg ?? theme.muted, children: hintText({
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface PromptCaretProps {
|
|
3
|
+
text: string;
|
|
4
|
+
active: boolean;
|
|
5
|
+
reducedMotion: boolean;
|
|
6
|
+
colorLevel: 0 | 1 | 2 | 3;
|
|
7
|
+
color?: string;
|
|
8
|
+
resetKey: object;
|
|
9
|
+
}
|
|
10
|
+
/** The only blinking state belongs to this leaf, never to the editor. */
|
|
11
|
+
export declare function PromptCaret({ text, active, reducedMotion, colorLevel, color, resetKey, }: PromptCaretProps): React.ReactElement;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useLayoutEffect, useState } from 'react';
|
|
3
|
+
import { Text } from 'ink';
|
|
4
|
+
import { displayWidth } from './composer-rows.js';
|
|
5
|
+
function monochromeMarker(text) {
|
|
6
|
+
// Ink's output grid advances for combining marks, even though they have no
|
|
7
|
+
// terminal width. Retain them in both phases so its cell count stays stable.
|
|
8
|
+
const replace = (marker) => Array.from(text, (point) => {
|
|
9
|
+
const width = displayWidth(point);
|
|
10
|
+
return width > 0 ? marker.repeat(width) : point;
|
|
11
|
+
}).join('');
|
|
12
|
+
const underline = replace('_');
|
|
13
|
+
return underline === text ? replace('^') : underline;
|
|
14
|
+
}
|
|
15
|
+
/** The only blinking state belongs to this leaf, never to the editor. */
|
|
16
|
+
export function PromptCaret({ text, active, reducedMotion, colorLevel, color, resetKey, }) {
|
|
17
|
+
const [visible, setVisible] = useState(true);
|
|
18
|
+
useLayoutEffect(() => {
|
|
19
|
+
setVisible(true);
|
|
20
|
+
if (!active || reducedMotion)
|
|
21
|
+
return;
|
|
22
|
+
const timer = setInterval(() => setVisible((previous) => !previous), 500);
|
|
23
|
+
return () => clearInterval(timer);
|
|
24
|
+
}, [resetKey, active, reducedMotion]);
|
|
25
|
+
const bright = active && (reducedMotion || visible);
|
|
26
|
+
let content = text;
|
|
27
|
+
if (bright && colorLevel === 0) {
|
|
28
|
+
content = monochromeMarker(text);
|
|
29
|
+
}
|
|
30
|
+
return _jsx(Text, { color: color, inverse: bright && colorLevel > 0, children: content });
|
|
31
|
+
}
|
package/dist/ui/PromptInput.d.ts
CHANGED
|
@@ -31,6 +31,9 @@ export interface CommandOption {
|
|
|
31
31
|
description: string;
|
|
32
32
|
}
|
|
33
33
|
interface PromptInputProps {
|
|
34
|
+
cols?: number;
|
|
35
|
+
cursorVisible?: boolean;
|
|
36
|
+
onInteraction?: () => void;
|
|
34
37
|
isActive: boolean;
|
|
35
38
|
running: boolean;
|
|
36
39
|
history: string[];
|
|
@@ -41,15 +44,18 @@ interface PromptInputProps {
|
|
|
41
44
|
/**
|
|
42
45
|
* Row budget for the completion popup (§5.2 / R-14).
|
|
43
46
|
*
|
|
44
|
-
* The popup
|
|
45
|
-
*
|
|
46
|
-
* status bar past the frame height, where the root box's `overflow: hidden`
|
|
47
|
-
* clips it away — the one piece of chrome R2 promises is always visible.
|
|
48
|
-
* `App` derives this from `budget.ts` so the popup shrinks instead.
|
|
47
|
+
* The popup shares the scrolling footer with the editor. Its height budget
|
|
48
|
+
* reserves visible message rows without changing the viewport or TODO rail.
|
|
49
49
|
*/
|
|
50
50
|
popupMaxRows?: number;
|
|
51
|
+
/** Total menu rows, including borders and the overflow hint. */
|
|
52
|
+
popupMaxHeight?: number;
|
|
53
|
+
onPopupRowsChange?: (rows: number) => void;
|
|
51
54
|
onSubmit: (text: string) => void;
|
|
52
55
|
onHelp?: () => void;
|
|
56
|
+
onEscape?: () => void;
|
|
57
|
+
onEscapeDismiss?: () => void;
|
|
58
|
+
reducedMotion?: boolean;
|
|
53
59
|
/**
|
|
54
60
|
* Mirror of the session mode. Read for the placeholder only — the toggle
|
|
55
61
|
* itself belongs to `App`, and this component's job is to get out of its way
|
|
@@ -173,5 +179,5 @@ export declare function fileTokenAt(buffer: string, cursor: number): {
|
|
|
173
179
|
end: number;
|
|
174
180
|
query: string;
|
|
175
181
|
} | null;
|
|
176
|
-
export declare function PromptInput({ isActive, running, history, commands, cwd, theme, caps, popupMaxRows, agentMode, onSubmit, onHelp, bordered, onDraftChange, onNotice, scrolledLines, }: PromptInputProps): React.ReactElement;
|
|
182
|
+
export declare function PromptInput({ cols: colsProp, cursorVisible, onInteraction, isActive, running, history, commands, cwd, theme, caps, popupMaxRows, popupMaxHeight, onPopupRowsChange, agentMode, onSubmit, onHelp, onEscape, onEscapeDismiss, reducedMotion, bordered, onDraftChange, onNotice, scrolledLines, }: PromptInputProps): React.ReactElement;
|
|
177
183
|
export {};
|
package/dist/ui/PromptInput.js
CHANGED
|
@@ -19,12 +19,14 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
19
19
|
* stays a plain `useState` because it is written from the debounced glob effect
|
|
20
20
|
* rather than from a key.
|
|
21
21
|
*/
|
|
22
|
-
import { useEffect, useReducer, useRef, useState } from 'react';
|
|
22
|
+
import { useEffect, useLayoutEffect, useMemo, useReducer, useRef, useState } from 'react';
|
|
23
23
|
import { Box, Text, useInput, useStdout } from 'ink';
|
|
24
24
|
import { glob as tinyGlob } from 'tinyglobby';
|
|
25
25
|
import { pickGlyphs } from './glyphs.js';
|
|
26
|
+
import { PromptCaret } from './PromptCaret.js';
|
|
26
27
|
import { recognize } from '../input/keymap.js';
|
|
27
28
|
import { AutocompletePopup } from './AutocompletePopup.js';
|
|
29
|
+
import { buildAutocompleteLayout } from './layout/autocomplete.js';
|
|
28
30
|
import { editorReducer, INITIAL_EDITOR_STATE, } from './editor-reducer.js';
|
|
29
31
|
import { hasPasteFrame, splitPasteFrames } from './paste-frames.js';
|
|
30
32
|
import { allocatePasteId, expandPastes, expandRangeOverTokens } from './paste-tokens.js';
|
|
@@ -42,8 +44,8 @@ const CHIP_FULL_MIN_COLS = 64;
|
|
|
42
44
|
* reads `999+`.
|
|
43
45
|
*/
|
|
44
46
|
const CHIP_MAX_N = 999;
|
|
45
|
-
/** `↓ 999+
|
|
46
|
-
const CHIP_FULL_CELLS =
|
|
47
|
+
/** `↓ 999+ lines below · PgDn` — the widest form the full branch can produce. */
|
|
48
|
+
const CHIP_FULL_CELLS = 27;
|
|
47
49
|
/** `↓999+` — the widest form the terse branch can produce. */
|
|
48
50
|
const CHIP_TERSE_CELLS = 6;
|
|
49
51
|
/**
|
|
@@ -63,7 +65,7 @@ export function scrollChip(scrolledLines, cols, glyphs) {
|
|
|
63
65
|
}
|
|
64
66
|
const noun = n === 1 ? 'line' : 'lines';
|
|
65
67
|
return {
|
|
66
|
-
text: `${glyphs.arrowDown} ${shown}
|
|
68
|
+
text: `${glyphs.arrowDown} ${shown} ${noun} below ${glyphs.midDot} PgDn`,
|
|
67
69
|
cells: CHIP_FULL_CELLS,
|
|
68
70
|
};
|
|
69
71
|
}
|
|
@@ -246,7 +248,7 @@ function isControlSeq(input) {
|
|
|
246
248
|
// ---------------------------------------------------------------------------
|
|
247
249
|
// Component
|
|
248
250
|
// ---------------------------------------------------------------------------
|
|
249
|
-
export function PromptInput({ isActive, running, history, commands, cwd, theme, caps, popupMaxRows, agentMode, onSubmit, onHelp, bordered, onDraftChange, onNotice, scrolledLines = 0, }) {
|
|
251
|
+
export function PromptInput({ cols: colsProp, cursorVisible = true, onInteraction, isActive, running, history, commands, cwd, theme, caps, popupMaxRows, popupMaxHeight, onPopupRowsChange, agentMode, onSubmit, onHelp, onEscape, onEscapeDismiss, reducedMotion = false, bordered, onDraftChange, onNotice, scrolledLines = 0, }) {
|
|
250
252
|
const { stdout } = useStdout();
|
|
251
253
|
const [editor, dispatch] = useReducer(editorReducer, INITIAL_EDITOR_STATE);
|
|
252
254
|
const { buffer, cursor, historyIndex, dismissed, sel } = editor;
|
|
@@ -263,6 +265,19 @@ export function PromptInput({ isActive, running, history, commands, cwd, theme,
|
|
|
263
265
|
? fileMatches.map((p) => ({ label: p }))
|
|
264
266
|
: [];
|
|
265
267
|
const clampedSel = popupItems.length > 0 ? Math.min(sel, popupItems.length - 1) : 0;
|
|
268
|
+
const popupLayout = buildAutocompleteLayout({
|
|
269
|
+
itemCount: isActive ? popupItems.length : 0,
|
|
270
|
+
selected: clampedSel,
|
|
271
|
+
maxRows: popupMaxRows,
|
|
272
|
+
maxHeight: popupMaxHeight,
|
|
273
|
+
});
|
|
274
|
+
const popupVisible = isActive && popupLayout.rowCount > 0;
|
|
275
|
+
// Report on every mount, including zero. Cleanup belongs to a separate
|
|
276
|
+
// effect so changing height does not publish an intermediate zero budget.
|
|
277
|
+
useLayoutEffect(() => {
|
|
278
|
+
onPopupRowsChange?.(popupLayout.rowCount);
|
|
279
|
+
}, [onPopupRowsChange, popupLayout.rowCount]);
|
|
280
|
+
useLayoutEffect(() => () => onPopupRowsChange?.(0), [onPopupRowsChange]);
|
|
266
281
|
// --- Debounced `@file` scan. ---------------------------------------------
|
|
267
282
|
useEffect(() => {
|
|
268
283
|
if (fileQuery === null || fileQuery.length < 1) {
|
|
@@ -300,7 +315,7 @@ export function PromptInput({ isActive, running, history, commands, cwd, theme,
|
|
|
300
315
|
// Deriving it twice — once for the frame and once for the budget — is exactly
|
|
301
316
|
// the trap `BottomStatusRow.tsx:5-27` documents.
|
|
302
317
|
const glyphs = pickGlyphs(caps);
|
|
303
|
-
const cols = stdout?.columns ?? 80;
|
|
318
|
+
const cols = colsProp ?? stdout?.columns ?? 80;
|
|
304
319
|
const terminalRows = stdout?.rows ?? 24;
|
|
305
320
|
// Full-screen only: inline mode has no self-drawn viewport, so `scrolledLines`
|
|
306
321
|
// is always 0 there and the branch is unreachable rather than merely unused.
|
|
@@ -316,7 +331,7 @@ export function PromptInput({ isActive, running, history, commands, cwd, theme,
|
|
|
316
331
|
cursor,
|
|
317
332
|
cols: wrapCols,
|
|
318
333
|
maxRows: draftMaxRows(terminalRows),
|
|
319
|
-
active: isActive,
|
|
334
|
+
active: isActive && cursorVisible,
|
|
320
335
|
});
|
|
321
336
|
// TWO PASSES, AND THE SECOND ONE IS LOAD-BEARING (I-8). The overflow indicator
|
|
322
337
|
// rides the last rendered row INSIDE the box the draft wraps in, so its cells
|
|
@@ -334,6 +349,7 @@ export function PromptInput({ isActive, running, history, commands, cwd, theme,
|
|
|
334
349
|
const usableCols = overflows ? Math.max(8, baseCols - OVERFLOW_CELLS - 1) : baseCols;
|
|
335
350
|
const layout = overflows ? measure(usableCols) : wide;
|
|
336
351
|
const draftRowCount = Math.max(1, layout.rows.length);
|
|
352
|
+
const caretResetKey = useMemo(() => ({}), [buffer, cursor, isActive, usableCols]);
|
|
337
353
|
// --- Draft presence and height, reported only on a transition. -----------
|
|
338
354
|
// Notifying on every keystroke would re-render the whole App tree per key. The
|
|
339
355
|
// row count changes on a line break or a wrap boundary, not per character
|
|
@@ -354,6 +370,7 @@ export function PromptInput({ isActive, running, history, commands, cwd, theme,
|
|
|
354
370
|
// by line. Two dispatches from one key would put the legacy root back to two
|
|
355
371
|
// commits and undo the whole of F3 for that branch.
|
|
356
372
|
const insert = (text) => {
|
|
373
|
+
onInteraction?.();
|
|
357
374
|
dispatch({ type: 'insert', text });
|
|
358
375
|
};
|
|
359
376
|
/**
|
|
@@ -365,6 +382,7 @@ export function PromptInput({ isActive, running, history, commands, cwd, theme,
|
|
|
365
382
|
* needs. `clear` releases every payload in the same dispatch.
|
|
366
383
|
*/
|
|
367
384
|
const submit = (text) => {
|
|
385
|
+
onInteraction?.();
|
|
368
386
|
onSubmit(expandPastes(text, editor.pastes));
|
|
369
387
|
dispatch({ type: 'clear' });
|
|
370
388
|
};
|
|
@@ -390,6 +408,7 @@ export function PromptInput({ isActive, running, history, commands, cwd, theme,
|
|
|
390
408
|
dispatch({ type: 'recall', buffer: value, cursor: value.length, historyIndex: idx });
|
|
391
409
|
};
|
|
392
410
|
const completeSelection = () => {
|
|
411
|
+
onInteraction?.();
|
|
393
412
|
const chosen = popupItems[clampedSel];
|
|
394
413
|
if (!chosen)
|
|
395
414
|
return;
|
|
@@ -403,6 +422,7 @@ export function PromptInput({ isActive, running, history, commands, cwd, theme,
|
|
|
403
422
|
}
|
|
404
423
|
};
|
|
405
424
|
const verticalOrHistory = (dir) => {
|
|
425
|
+
onInteraction?.();
|
|
406
426
|
const moved = moveVertical(buffer, cursor, dir);
|
|
407
427
|
if (moved) {
|
|
408
428
|
dispatch({ type: 'moveCursor', cursor: moved.cursor });
|
|
@@ -427,13 +447,17 @@ export function PromptInput({ isActive, running, history, commands, cwd, theme,
|
|
|
427
447
|
// appears with a popup open and is therefore easy to ship (AC-P3 / R-P2).
|
|
428
448
|
if (key.tab && key.shift)
|
|
429
449
|
return;
|
|
450
|
+
if (key.pageUp || key.pageDown || (key.shift && (key.upArrow || key.downArrow)))
|
|
451
|
+
return;
|
|
430
452
|
// Popup navigation owns Up/Down/Tab/→/Enter/Esc while it is open.
|
|
431
|
-
if (
|
|
453
|
+
if (popupVisible) {
|
|
432
454
|
if (key.upArrow) {
|
|
455
|
+
onInteraction?.();
|
|
433
456
|
dispatch({ type: 'select', sel: Math.max(0, clampedSel - 1) });
|
|
434
457
|
return;
|
|
435
458
|
}
|
|
436
459
|
if (key.downArrow) {
|
|
460
|
+
onInteraction?.();
|
|
437
461
|
dispatch({ type: 'select', sel: Math.min(popupItems.length - 1, clampedSel + 1) });
|
|
438
462
|
return;
|
|
439
463
|
}
|
|
@@ -449,15 +473,19 @@ export function PromptInput({ isActive, running, history, commands, cwd, theme,
|
|
|
449
473
|
}
|
|
450
474
|
if (key.escape) {
|
|
451
475
|
dispatch({ type: 'dismiss' });
|
|
476
|
+
onEscapeDismiss?.();
|
|
452
477
|
return;
|
|
453
478
|
}
|
|
454
479
|
// Any other key falls through so typing keeps filtering the popup.
|
|
455
480
|
}
|
|
456
|
-
if (key.escape)
|
|
457
|
-
|
|
481
|
+
if (key.escape) {
|
|
482
|
+
onEscape?.();
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
458
485
|
// Line-editing intents (Home/End/word/kill) recognized from raw keys.
|
|
459
486
|
const intent = recognize(input, key);
|
|
460
487
|
if (intent) {
|
|
488
|
+
onInteraction?.();
|
|
461
489
|
const next = applyEdit(buffer, cursor, intent);
|
|
462
490
|
dispatch({ type: 'replace', buffer: next.buffer, cursor: next.cursor });
|
|
463
491
|
return;
|
|
@@ -475,6 +503,7 @@ export function PromptInput({ isActive, running, history, commands, cwd, theme,
|
|
|
475
503
|
return;
|
|
476
504
|
}
|
|
477
505
|
if (key.backspace || key.delete) {
|
|
506
|
+
onInteraction?.();
|
|
478
507
|
// The `cursor > 0` guard lives in the reducer now, so this branch is one
|
|
479
508
|
// unconditional dispatch and a no-op at the left edge returns the same
|
|
480
509
|
// state object — which React bails out on, exactly as the old
|
|
@@ -483,11 +512,15 @@ export function PromptInput({ isActive, running, history, commands, cwd, theme,
|
|
|
483
512
|
return;
|
|
484
513
|
}
|
|
485
514
|
if (key.leftArrow) {
|
|
486
|
-
|
|
515
|
+
onInteraction?.();
|
|
516
|
+
const step = cursor >= 2 && (buffer.codePointAt(cursor - 2) ?? 0) > 0xffff ? 2 : 1;
|
|
517
|
+
dispatch({ type: 'moveCursor', cursor: Math.max(0, cursor - step) });
|
|
487
518
|
return;
|
|
488
519
|
}
|
|
489
520
|
if (key.rightArrow) {
|
|
490
|
-
|
|
521
|
+
onInteraction?.();
|
|
522
|
+
const step = (buffer.codePointAt(cursor) ?? 0) > 0xffff ? 2 : 1;
|
|
523
|
+
dispatch({ type: 'moveCursor', cursor: Math.min(buffer.length, cursor + step) });
|
|
491
524
|
return;
|
|
492
525
|
}
|
|
493
526
|
// `!key.shift` is load-bearing: Shift+↑/↓ is the viewport's line-scroll
|
|
@@ -525,6 +558,7 @@ export function PromptInput({ isActive, running, history, commands, cwd, theme,
|
|
|
525
558
|
onNotice?.('warn', refusal);
|
|
526
559
|
return;
|
|
527
560
|
}
|
|
561
|
+
onInteraction?.();
|
|
528
562
|
dispatch({ type: 'input', segments });
|
|
529
563
|
return;
|
|
530
564
|
}
|
|
@@ -535,7 +569,7 @@ export function PromptInput({ isActive, running, history, commands, cwd, theme,
|
|
|
535
569
|
const marker = running ? glyphs.steer : glyphs.caret;
|
|
536
570
|
const markerColor = running ? theme.toolRunning : theme.primary;
|
|
537
571
|
const placeholder = running
|
|
538
|
-
? `Type to steer the run, Esc to
|
|
572
|
+
? `Type to steer the run, Esc twice to interrupt${glyphs.ellipsis}`
|
|
539
573
|
: agentMode === 'plan'
|
|
540
574
|
? `Describe what you want to build; I'll research and plan it first${glyphs.ellipsis}`
|
|
541
575
|
: `Send a message (/ for commands, @ for files)${glyphs.ellipsis}`;
|
|
@@ -548,9 +582,9 @@ export function PromptInput({ isActive, running, history, commands, cwd, theme,
|
|
|
548
582
|
// The caret is drawn as one inverse cell, split at the column
|
|
549
583
|
// `layoutComposer` reported — the same width table it wrapped with (I-9).
|
|
550
584
|
const split = splitRowAtColumn(row, layout.cursorCol);
|
|
551
|
-
return (_jsxs(_Fragment, { children: [renderSegments(split.before, `b${i}.`), _jsx(
|
|
585
|
+
return (_jsxs(_Fragment, { children: [renderSegments(split.before, `b${i}.`), _jsx(PromptCaret, { text: split.at ? split.at.text : ' ', active: isActive && cursorVisible, reducedMotion: reducedMotion, colorLevel: caps.colorLevel, resetKey: caretResetKey, color: split.at?.kind === 'token' ? tokenColor : undefined }), renderSegments(split.after, `a${i}.`)] }));
|
|
552
586
|
});
|
|
553
|
-
const inputRow = (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: markerColor, bold: true, children: [marker, ' '] }), _jsx(Box, { flexDirection: "column", flexGrow: 1, flexShrink: 1, children: buffer.length === 0 ? (
|
|
587
|
+
const inputRow = (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: markerColor, bold: true, children: [marker, ' '] }), _jsx(Box, { flexDirection: "column", flexGrow: 1, flexShrink: 1, children: buffer.length === 0 ? (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [_jsx(PromptCaret, { text: String.fromCodePoint(placeholder.codePointAt(0)), active: isActive && cursorVisible, reducedMotion: reducedMotion, colorLevel: caps.colorLevel, color: theme.muted, resetKey: caretResetKey }), placeholder.slice(String.fromCodePoint(placeholder.codePointAt(0)).length)] })) : (rowNodes.map((node, i) => overflow && i === rowNodes.length - 1 ? (
|
|
554
588
|
// The overflow indicator rides the LAST rendered row rather than
|
|
555
589
|
// taking one of its own: an extra row would put the composer's
|
|
556
590
|
// real height one above the `draftRows` it just reported (I-8).
|
|
@@ -565,5 +599,5 @@ export function PromptInput({ isActive, running, history, commands, cwd, theme,
|
|
|
565
599
|
// the user is typing — `↓ 9` becomes `↓ 10` becomes `↓ 100` — and every
|
|
566
600
|
// change would otherwise re-wrap a draft that fills the line.
|
|
567
601
|
_jsx(Box, { flexShrink: 0, alignSelf: "flex-start", marginLeft: 1, width: chip.cells, justifyContent: "flex-end", children: _jsx(Text, { wrap: "truncate", color: theme.hintFg ?? theme.muted, children: chip.text }) }))] }));
|
|
568
|
-
return (_jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [
|
|
602
|
+
return (_jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [popupVisible && (_jsx(AutocompletePopup, { items: popupItems, selected: clampedSel, theme: theme, caps: caps, maxRows: popupMaxRows, layout: popupLayout })), bordered ? (_jsx(Box, { flexDirection: "column", flexShrink: 0, borderStyle: glyphs.boxStyle, borderColor: bordered.color, paddingX: 1, children: inputRow })) : (inputRow)] }));
|
|
569
603
|
}
|
package/dist/ui/StatusBar.d.ts
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Status bar (spec §4.8). Width-aware, borderless, EXACTLY one row: a left
|
|
3
3
|
* cluster (run status, model, thinking level) and a right cluster (context
|
|
4
|
-
* gauge,
|
|
5
|
-
* Clusters drop out gracefully on narrow terminals.
|
|
4
|
+
* gauge, occupancy pair, session total, cost, tokens/sec + elapsed while
|
|
5
|
+
* running, off-bottom `↑N`). Clusters drop out gracefully on narrow terminals.
|
|
6
|
+
*
|
|
7
|
+
* THE RIGHT CLUSTER'S WIDTH LADDER (context-usage-gauge-accuracy §3.6):
|
|
8
|
+
*
|
|
9
|
+
* >= 96 [####....] 43% 86.0k/200.0k total 1.2M^ 48.0k v $3.21 12 tok/s 1m02s
|
|
10
|
+
* 72-95 [####....] 43% 86.0k/200.0k $3.21
|
|
11
|
+
* 60-71 [####....] 43% $3.21
|
|
12
|
+
* < 60 43% $3.21
|
|
6
13
|
*
|
|
7
14
|
* Dropping the round border here buys back 3 rows of a 24-row terminal — an
|
|
8
15
|
* eighth of the screen was being spent framing a single line of text.
|
|
@@ -11,14 +18,24 @@ import React from 'react';
|
|
|
11
18
|
import type { Theme } from './theme.js';
|
|
12
19
|
import type { TermCapabilities } from './capabilities.js';
|
|
13
20
|
import type { UsageTotal } from '../agent/reducer.js';
|
|
21
|
+
import type { ContextUsageSnapshot } from '../compaction/types.js';
|
|
14
22
|
import { type AgentMode } from '../agent/agent-mode.js';
|
|
15
23
|
interface StatusBarProps {
|
|
24
|
+
interruptHint?: string;
|
|
16
25
|
model: string;
|
|
17
26
|
provider: string;
|
|
18
27
|
usageTotal: UsageTotal;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Context occupancy, as one object (context-usage-gauge-accuracy §4.3).
|
|
30
|
+
*
|
|
31
|
+
* IT REPLACED FOUR PARALLEL PROPS - `contextTokens`, `contextWindow`,
|
|
32
|
+
* `contextWindowKnown`, `contextEstimated`. Four props describing one reading
|
|
33
|
+
* are four chances for a caller to update three of them, which is the props-
|
|
34
|
+
* layer form of the "many writers" defect this feature removed from the
|
|
35
|
+
* reducer. `usageTotal` stays separate because it genuinely IS another
|
|
36
|
+
* quantity: session spend, not occupancy.
|
|
37
|
+
*/
|
|
38
|
+
context: ContextUsageSnapshot;
|
|
22
39
|
status: 'idle' | 'running';
|
|
23
40
|
elapsedMs: number;
|
|
24
41
|
thinkingLevel: string;
|
|
@@ -187,16 +204,6 @@ interface StatusBarProps {
|
|
|
187
204
|
warn: number;
|
|
188
205
|
high: number;
|
|
189
206
|
};
|
|
190
|
-
/**
|
|
191
|
-
* Whether `contextTokens` is a DERIVED figure rather than a measured one.
|
|
192
|
-
*
|
|
193
|
-
* It WIDENS THE EXISTING `~`, it does not add a second marker. Today that
|
|
194
|
-
* prefix means "the window is a guess" (`contextWindowKnown`); with compaction
|
|
195
|
-
* it also has to mean "the occupancy is a guess", because the bar is written
|
|
196
|
-
* from an estimate for one turn after every compaction so it can fall
|
|
197
|
-
* immediately (§6.2 / AC-5).
|
|
198
|
-
*/
|
|
199
|
-
contextEstimated?: boolean;
|
|
200
207
|
}
|
|
201
208
|
export declare function StatusBar(props: StatusBarProps): React.ReactElement;
|
|
202
209
|
export {};
|