@aragon-agent/cli 0.6.12 → 0.6.13
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/README.md +81 -15
- package/dist/agent/controller.d.ts +29 -1
- package/dist/agent/controller.js +123 -22
- package/dist/agent/model-profile-settings.d.ts +31 -0
- package/dist/agent/model-profile-settings.js +199 -0
- package/dist/agent/queued-messages.d.ts +12 -0
- package/dist/agent/queued-messages.js +18 -0
- package/dist/agent/reducer.d.ts +13 -1
- package/dist/agent/reducer.js +51 -12
- package/dist/cli.js +15 -1
- package/dist/commands/builtins.js +66 -39
- package/dist/commands/queue-command.d.ts +3 -0
- package/dist/commands/queue-command.js +5 -0
- package/dist/commands/registry.d.ts +3 -0
- package/dist/compaction/child.d.ts +4 -2
- package/dist/compaction/child.js +4 -1
- package/dist/compaction/compactor.d.ts +7 -2
- package/dist/compaction/compactor.js +13 -11
- package/dist/compaction/context-command.js +5 -0
- package/dist/compaction/wiring.d.ts +4 -3
- package/dist/compaction/wiring.js +6 -1
- package/dist/config/cli-commands.d.ts +2 -0
- package/dist/config/cli-commands.js +16 -4
- package/dist/config/load.d.ts +2 -1
- package/dist/config/load.js +15 -8
- package/dist/config/model-profile-resolution.d.ts +20 -0
- package/dist/config/model-profile-resolution.js +81 -0
- package/dist/config/model-profile-store.d.ts +67 -0
- package/dist/config/model-profile-store.js +153 -0
- package/dist/config/model-profiles.d.ts +44 -0
- package/dist/config/model-profiles.js +121 -0
- package/dist/config/schema.d.ts +9 -0
- package/dist/config/store.d.ts +11 -1
- package/dist/config/store.js +43 -27
- package/dist/fast/resolve.d.ts +2 -1
- package/dist/fast/resolve.js +6 -4
- package/dist/fast/wiring.d.ts +5 -3
- package/dist/fast/wiring.js +8 -4
- package/dist/input/stdin-filter.js +3 -2
- package/dist/logging/install.js +4 -2
- package/dist/logging/secret-registry.d.ts +2 -0
- package/dist/logging/secret-registry.js +18 -0
- package/dist/team/runtime.d.ts +2 -1
- package/dist/team/subagent.d.ts +3 -1
- package/dist/team/subagent.js +3 -1
- package/dist/ui/ActivityLine.d.ts +6 -70
- package/dist/ui/ActivityLine.js +12 -34
- package/dist/ui/App.d.ts +1 -0
- package/dist/ui/App.js +113 -205
- package/dist/ui/BottomStatusRow.d.ts +6 -86
- package/dist/ui/BottomStatusRow.js +11 -26
- package/dist/ui/Composer.d.ts +16 -83
- package/dist/ui/Composer.js +9 -100
- package/dist/ui/PromptInput.d.ts +4 -1
- package/dist/ui/PromptInput.js +41 -34
- package/dist/ui/QueuePanel.d.ts +8 -0
- package/dist/ui/QueuePanel.js +9 -0
- package/dist/ui/QueueStatusRow.d.ts +2 -0
- package/dist/ui/QueueStatusRow.js +1 -1
- package/dist/ui/StatusBar.d.ts +13 -149
- package/dist/ui/StatusBar.js +27 -142
- package/dist/ui/clipboard-task.d.ts +29 -0
- package/dist/ui/clipboard-task.js +66 -0
- package/dist/ui/clipboard.d.ts +29 -50
- package/dist/ui/clipboard.js +141 -75
- package/dist/ui/composer-limits.d.ts +3 -4
- package/dist/ui/composer-limits.js +4 -9
- package/dist/ui/composer-rows.d.ts +1 -1
- package/dist/ui/composer-rows.js +24 -137
- package/dist/ui/editor-navigation.d.ts +45 -0
- package/dist/ui/editor-navigation.js +85 -0
- package/dist/ui/editor-reducer.d.ts +4 -0
- package/dist/ui/editor-reducer.js +29 -15
- package/dist/ui/interaction-copy.d.ts +77 -0
- package/dist/ui/interaction-copy.js +40 -0
- package/dist/ui/interaction-hints.d.ts +18 -0
- package/dist/ui/interaction-hints.js +92 -0
- package/dist/ui/layout/budget.js +1 -1
- package/dist/ui/layout/queue-layout.d.ts +19 -0
- package/dist/ui/layout/queue-layout.js +26 -0
- package/dist/ui/layout/status-layout.d.ts +55 -0
- package/dist/ui/layout/status-layout.js +109 -0
- package/dist/ui/layout/todo-layout.d.ts +1 -0
- package/dist/ui/layout/todo-layout.js +1 -1
- package/dist/ui/overlays/HelpOverlay.js +8 -8
- package/dist/ui/overlays/ModelProfileEditor.d.ts +12 -0
- package/dist/ui/overlays/ModelProfileEditor.js +103 -0
- package/dist/ui/overlays/ModelProfilePicker.d.ts +35 -0
- package/dist/ui/overlays/ModelProfilePicker.js +167 -0
- package/dist/ui/overlays/QueueOverlay.d.ts +22 -0
- package/dist/ui/overlays/QueueOverlay.js +79 -0
- package/dist/ui/overlays/SettingsScreen.d.ts +9 -2
- package/dist/ui/overlays/SettingsScreen.js +347 -3
- package/dist/ui/overlays/model-profile-state.d.ts +74 -0
- package/dist/ui/overlays/model-profile-state.js +119 -0
- package/dist/ui/run-status-row.d.ts +7 -19
- package/dist/ui/run-status-row.js +2 -18
- package/dist/ui/selection/selection-controller.d.ts +2 -2
- package/dist/ui/selection/selection-controller.js +1 -0
- package/dist/ui/use-wheel-routing.js +2 -1
- package/package.json +2 -2
|
@@ -1,27 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Box, Text } from 'ink';
|
|
3
|
-
import {
|
|
4
|
-
export function BottomStatusRow(
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
return (
|
|
13
|
-
// `flexDirection` SPELLED OUT even though `row` is Yoga's default: it is
|
|
14
|
-
// what keeps two children on ONE row, and the header above turns on that
|
|
15
|
-
// being true. Left implicit, it reads as an oversight next to the child
|
|
16
|
-
// below that declares `column` — and "make them match" is a one-word edit
|
|
17
|
-
// that doubles the row, takes it from the transcript, and desynchronises
|
|
18
|
-
// every `viewportRows()` consumer without failing a type or a lint.
|
|
19
|
-
_jsxs(Box, { flexDirection: "row", flexShrink: 0, children: [_jsx(Box, { flexShrink: 0, children: _jsxs(Text, { color: theme.thinking, children: [' ', spinner] }) }), _jsx(Box, { flexDirection: "column", flexGrow: 1, flexShrink: 1, overflow: "hidden", children: _jsx(ToastStack, { toasts: toasts, theme: theme }) })] }));
|
|
20
|
-
}
|
|
21
|
-
if (activity)
|
|
22
|
-
return _jsx(Box, { flexShrink: 0, children: activity });
|
|
23
|
-
if (update)
|
|
24
|
-
return _jsx(Box, { flexShrink: 0, children: update });
|
|
25
|
-
// The blank full-screen row that IS the budget must keep being emitted.
|
|
26
|
-
return _jsx(ToastStack, { toasts: toasts, theme: theme });
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text, useStdout } from 'ink';
|
|
3
|
+
import { buildActionClauses } from './interaction-hints.js';
|
|
4
|
+
export function BottomStatusRow(props) {
|
|
5
|
+
const { stdout } = useStdout();
|
|
6
|
+
const toast = props.toasts[props.toasts.length - 1];
|
|
7
|
+
const hints = props.hints ?? { cols: stdout?.columns ?? 80,
|
|
8
|
+
interactionPhase: props.activity ? 'running' : 'idle' };
|
|
9
|
+
const clauses = buildActionClauses({ ...hints, toast: hints.toast ?? toast?.text,
|
|
10
|
+
updateAvailable: hints.updateAvailable ?? Boolean(props.update) });
|
|
11
|
+
return _jsx(Box, { height: 1, width: hints.cols, flexShrink: 0, children: _jsxs(Text, { color: toast?.level === 'error' ? props.theme.noticeError : props.theme.muted, children: [' ', clauses.join(' | ')] }) });
|
|
27
12
|
}
|
package/dist/ui/Composer.d.ts
CHANGED
|
@@ -1,17 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Composer (spec §4.7) — the thin shell around the existing `PromptInput`.
|
|
3
|
-
*
|
|
4
|
-
* Owns exactly two things: the semantic border color and the hint row. The
|
|
5
|
-
* editor's pure helpers (`applyEdit` / `moveVertical` / `slashSuggestions` /
|
|
6
|
-
* `fileTokenAt`) are untouched, so `input.test.ts` keeps passing.
|
|
7
|
-
*
|
|
8
|
-
* The draft flag is held HERE rather than in `App`: the border has to react to
|
|
9
|
-
* "the input is no longer empty", and routing that through App would re-render
|
|
10
|
-
* the transcript on the first keystroke of every message.
|
|
11
|
-
*
|
|
12
|
-
* The fixed status row retains emergency and history-navigation hints while
|
|
13
|
-
* this footer scrolls outside the viewport. Editor state stays mounted.
|
|
14
|
-
*/
|
|
1
|
+
/** Input border and draft shell. Global action hints and activity live below the viewport. */
|
|
15
2
|
import React from 'react';
|
|
16
3
|
import { type DOMElement } from 'ink';
|
|
17
4
|
import type { Theme } from './theme.js';
|
|
@@ -19,14 +6,9 @@ import type { TermCapabilities } from './capabilities.js';
|
|
|
19
6
|
import { pickGlyphs } from './glyphs.js';
|
|
20
7
|
import type { ComposerSubmitResult } from './composer-input.js';
|
|
21
8
|
import { type CommandOption } from './PromptInput.js';
|
|
22
|
-
import {
|
|
9
|
+
import type { RunActivity } from './run-status-row.js';
|
|
23
10
|
import { type AgentMode } from '../agent/agent-mode.js';
|
|
24
|
-
/**
|
|
25
|
-
* The run status row (tui-scrollbar-edge-and-run-row §3.3): the spinner, the phrase
|
|
26
|
-
* and the `queue / interrupt / exit` clauses on ONE row above the input box.
|
|
27
|
-
* `App` passes it only while a run is in flight AND the idle hint row would have
|
|
28
|
-
* been shown, so enabling it never changes the footer's height.
|
|
29
|
-
*/
|
|
11
|
+
/** Legacy caller shape retained for source compatibility; Composer no longer mounts it. */
|
|
30
12
|
export interface RunRowProps {
|
|
31
13
|
activity: RunActivity;
|
|
32
14
|
/** The row is in the viewport with no overlay: draw the animated spinner. */
|
|
@@ -35,9 +17,11 @@ export interface RunRowProps {
|
|
|
35
17
|
rowRef?: React.RefObject<DOMElement>;
|
|
36
18
|
}
|
|
37
19
|
export interface ComposerProps {
|
|
38
|
-
/**
|
|
20
|
+
/** Compatibility prop; activity is now rendered by the global StatusBar. */
|
|
39
21
|
runRow?: RunRowProps | null;
|
|
40
22
|
cols?: number;
|
|
23
|
+
deleteDisambiguated?: boolean;
|
|
24
|
+
onCompletionContextChange?: (context: 'none' | 'slash' | 'file') => void;
|
|
41
25
|
cursorVisible?: boolean;
|
|
42
26
|
onInteraction?: () => void;
|
|
43
27
|
measureRef?: React.RefObject<DOMElement>;
|
|
@@ -52,48 +36,20 @@ export interface ComposerProps {
|
|
|
52
36
|
cwd: string;
|
|
53
37
|
/** Dropped on short terminals to buy back a row (§5.4). */
|
|
54
38
|
showHint: boolean;
|
|
55
|
-
/**
|
|
39
|
+
/** Compatibility prop; submission count no longer hides essential actions. */
|
|
56
40
|
submitCount: number;
|
|
57
|
-
/**
|
|
41
|
+
/** Compatibility prop; App owns secondary teaching-hint visibility. */
|
|
58
42
|
hintsEnabled: boolean;
|
|
59
|
-
/** Mirror of the controller's effective mode; drives the
|
|
43
|
+
/** Mirror of the controller's effective mode; drives the border. */
|
|
60
44
|
agentMode: AgentMode;
|
|
61
|
-
/**
|
|
62
|
-
* Which key this hint should NAME for the mode toggle
|
|
63
|
-
* (shift-tab-mode-toggle-still-dead-on-windows, C3-1).
|
|
64
|
-
*
|
|
65
|
-
* A PROP RATHER THAN A CONSTANT because on some consoles the constant is a
|
|
66
|
-
* lie. This row used to render `shift+tab` unconditionally, with no link to
|
|
67
|
-
* the capability check three files away, so the machines where that key is
|
|
68
|
-
* swallowed were the ones being told to press it - permanently, on every
|
|
69
|
-
* frame, while the one warning they got scrolled out of the transcript
|
|
70
|
-
* minutes earlier (analysis section 3.7). Defaults to the primary key, which
|
|
71
|
-
* is what every unaffected machine renders.
|
|
72
|
-
*/
|
|
45
|
+
/** Compatibility prop; the global action row names the effective mode key. */
|
|
73
46
|
modeToggleKey?: string;
|
|
74
47
|
popupMaxRows?: number;
|
|
75
48
|
popupMaxHeight?: number;
|
|
76
49
|
onPopupRowsChange?: (rows: number) => void;
|
|
77
|
-
/**
|
|
78
|
-
* Rows of newer output below the viewport, forwarded to the input row's chip
|
|
79
|
-
* (tui-selection-and-scroll-follow §4.2 / G7).
|
|
80
|
-
*
|
|
81
|
-
* IT IS STATE, NOT A FADEABLE HINT, and it therefore does NOT pass through the
|
|
82
|
-
* `showHint` / `hintsEnabled` gate below. Those two govern the teaching row —
|
|
83
|
-
* which retires keys the user has demonstrably learned and is dropped outright
|
|
84
|
-
* on a short terminal — and "you are 12 rows behind the newest output" is
|
|
85
|
-
* neither learnable nor optional. Same argument that keeps `esc abort`
|
|
86
|
-
* un-faded in `hintText`.
|
|
87
|
-
*/
|
|
50
|
+
/** Rows of newer output below the viewport, forwarded to the input's scroll chip. */
|
|
88
51
|
scrolledLines?: number;
|
|
89
|
-
/**
|
|
90
|
-
* Live background services (background-service-supervision §3.8), 0 when none.
|
|
91
|
-
*
|
|
92
|
-
* IT IS STATE, NOT A FADEABLE HINT, exactly like `scrolledLines` above: the
|
|
93
|
-
* clause it adds is the only visible way to stop something, and `hintText`
|
|
94
|
-
* below keeps it out of the faded form for the same reason `esc abort` is kept
|
|
95
|
-
* there.
|
|
96
|
-
*/
|
|
52
|
+
/** Compatibility prop; the global status and action rows show live services. */
|
|
97
53
|
services?: number;
|
|
98
54
|
/**
|
|
99
55
|
* The composer's DRAFT row count, forwarded upward (tui-paste-handling
|
|
@@ -113,27 +69,12 @@ export interface ComposerProps {
|
|
|
113
69
|
onSubmit: (text: string) => ComposerSubmitResult;
|
|
114
70
|
onHelp?: () => void;
|
|
115
71
|
}
|
|
116
|
-
/**
|
|
117
|
-
* The running-state hint as separate clauses, most urgent first.
|
|
118
|
-
*
|
|
119
|
-
* `hintText` joins them (its output is pinned by AC-43), and the run status row
|
|
120
|
-
* consumes the SAME array so it can drop whole clauses from the tail on a narrow
|
|
121
|
-
* terminal instead of cutting `interrupt` in half. One source, two consumers.
|
|
122
|
-
*/
|
|
72
|
+
/** Legacy helper retained for consumers and contract tests; not rendered by Composer. */
|
|
123
73
|
export declare function runningHintClauses(opts: {
|
|
124
74
|
glyphs: ReturnType<typeof pickGlyphs>;
|
|
125
75
|
services: number;
|
|
126
76
|
}): string[];
|
|
127
|
-
/**
|
|
128
|
-
* Build the hint row.
|
|
129
|
-
*
|
|
130
|
-
* The `running` branch is NEVER abbreviated, no matter how experienced the user
|
|
131
|
-
* is. Once the status bar's hint cluster was removed, the composer became the
|
|
132
|
-
* only place that names the abort key — and fading that out after eight
|
|
133
|
-
* submissions would leave a long-running task with no visible way to stop it.
|
|
134
|
-
* Progressive disclosure is for teaching discoverable things, not for retiring
|
|
135
|
-
* an emergency exit (§4.6 / A-14 / M-10b).
|
|
136
|
-
*/
|
|
77
|
+
/** Compatibility formatter. Product hints use interaction-hints.ts. */
|
|
137
78
|
declare function hintText(opts: {
|
|
138
79
|
running: boolean;
|
|
139
80
|
submitCount: number;
|
|
@@ -143,15 +84,7 @@ declare function hintText(opts: {
|
|
|
143
84
|
/** Live background services; 0 leaves both rows byte-identical to today's. */
|
|
144
85
|
services: number;
|
|
145
86
|
}): string;
|
|
146
|
-
/**
|
|
147
|
-
* `hintText`, for the interrupt-ladder tests (AC-43).
|
|
148
|
-
*
|
|
149
|
-
* EXPORTED UNDER A TEST-FACING NAME rather than exporting `hintText` itself, so
|
|
150
|
-
* the private helper stays private in the module graph and nothing in `src/`
|
|
151
|
-
* grows a second caller by accident. The row is the ONLY visible way to stop a
|
|
152
|
-
* process the agent left running, so it is worth asserting on directly rather
|
|
153
|
-
* than fishing the string out of a rendered frame.
|
|
154
|
-
*/
|
|
87
|
+
/** Compatibility export for existing hint contract tests. */
|
|
155
88
|
export declare const hintTextForTest: typeof hintText;
|
|
156
|
-
export declare function Composer({
|
|
89
|
+
export declare function Composer({ deleteDisambiguated, onCompletionContextChange, cols, cursorVisible, onInteraction, measureRef, isActive, reducedMotion, onEscape, onEscapeDismiss, running, history, commands, cwd, agentMode, popupMaxRows, popupMaxHeight, onPopupRowsChange, scrolledLines, onDraftRows, onNotice, theme, caps, onSubmit, onHelp, }: ComposerProps): React.ReactElement;
|
|
157
90
|
export {};
|
package/dist/ui/Composer.js
CHANGED
|
@@ -1,80 +1,26 @@
|
|
|
1
|
-
import { jsx as _jsx
|
|
2
|
-
/**
|
|
3
|
-
* Composer (spec §4.7) — the thin shell around the existing `PromptInput`.
|
|
4
|
-
*
|
|
5
|
-
* Owns exactly two things: the semantic border color and the hint row. The
|
|
6
|
-
* editor's pure helpers (`applyEdit` / `moveVertical` / `slashSuggestions` /
|
|
7
|
-
* `fileTokenAt`) are untouched, so `input.test.ts` keeps passing.
|
|
8
|
-
*
|
|
9
|
-
* The draft flag is held HERE rather than in `App`: the border has to react to
|
|
10
|
-
* "the input is no longer empty", and routing that through App would re-render
|
|
11
|
-
* the transcript on the first keystroke of every message.
|
|
12
|
-
*
|
|
13
|
-
* The fixed status row retains emergency and history-navigation hints while
|
|
14
|
-
* this footer scrolls outside the viewport. Editor state stays mounted.
|
|
15
|
-
*/
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/** Input border and draft shell. Global action hints and activity live below the viewport. */
|
|
16
3
|
import { useCallback, useState } from 'react';
|
|
17
|
-
import { Box
|
|
18
|
-
import stringWidth from 'string-width';
|
|
19
|
-
import { pickGlyphs } from './glyphs.js';
|
|
4
|
+
import { Box } from 'ink';
|
|
20
5
|
import { HINT_FADE_AFTER } from '../config/schema.js';
|
|
21
6
|
import { PromptInput } from './PromptInput.js';
|
|
22
|
-
import {
|
|
23
|
-
|
|
24
|
-
import { RUN_ROW_LEAD, planRunRow } from './run-status-row.js';
|
|
25
|
-
import { MODE_LABEL, MODE_TOGGLE_KEYS, nextMode } from '../agent/agent-mode.js';
|
|
26
|
-
/**
|
|
27
|
-
* The running-state hint as separate clauses, most urgent first.
|
|
28
|
-
*
|
|
29
|
-
* `hintText` joins them (its output is pinned by AC-43), and the run status row
|
|
30
|
-
* consumes the SAME array so it can drop whole clauses from the tail on a narrow
|
|
31
|
-
* terminal instead of cutting `interrupt` in half. One source, two consumers.
|
|
32
|
-
*/
|
|
7
|
+
import { MODE_LABEL, nextMode } from '../agent/agent-mode.js';
|
|
8
|
+
/** Legacy helper retained for consumers and contract tests; not rendered by Composer. */
|
|
33
9
|
export function runningHintClauses(opts) {
|
|
34
10
|
const { glyphs, services } = opts;
|
|
35
|
-
// `queue`, NOT `steer` (tui-shift-enter-copy-queue 5.5): the gesture's
|
|
36
|
-
// consequence now has a name in the transcript (`Queue: ...`), and one
|
|
37
|
-
// behaviour with two names is undiscoverable.
|
|
38
11
|
const clauses = [`${glyphs.enterKey} queue`, `esc${glyphs.times}2 interrupt`];
|
|
39
|
-
// Confirmation is named even when service controls also need space.
|
|
40
12
|
if (services > 0)
|
|
41
13
|
clauses.push(`ctrl+c stop ${services}`);
|
|
42
14
|
clauses.push(`ctrl+c${glyphs.times}2 exit`);
|
|
43
15
|
return clauses;
|
|
44
16
|
}
|
|
45
|
-
/**
|
|
46
|
-
* Build the hint row.
|
|
47
|
-
*
|
|
48
|
-
* The `running` branch is NEVER abbreviated, no matter how experienced the user
|
|
49
|
-
* is. Once the status bar's hint cluster was removed, the composer became the
|
|
50
|
-
* only place that names the abort key — and fading that out after eight
|
|
51
|
-
* submissions would leave a long-running task with no visible way to stop it.
|
|
52
|
-
* Progressive disclosure is for teaching discoverable things, not for retiring
|
|
53
|
-
* an emergency exit (§4.6 / A-14 / M-10b).
|
|
54
|
-
*/
|
|
17
|
+
/** Compatibility formatter. Product hints use interaction-hints.ts. */
|
|
55
18
|
function hintText(opts) {
|
|
56
19
|
const { running, submitCount, glyphs, mode, toggleKey, services } = opts;
|
|
57
20
|
const dot = ` ${glyphs.midDot} `;
|
|
58
|
-
// THE EXIT CLAUSE SURVIVES IN BOTH ROWS (P1-4). The obvious edit here is to
|
|
59
|
-
// replace `ctrl+c*2 exit` with `ctrl+c stop N` — and that would delete the
|
|
60
|
-
// only visible way to quit at exactly the moment Ctrl+C stops meaning "quit",
|
|
61
|
-
// on the screen a user reaches when something has already gone wrong. Both
|
|
62
|
-
// clauses are shown; the row is `wrap="truncate"` and they are ordered
|
|
63
|
-
// most-urgent-first, so a terminal too narrow for all of them drops the exit
|
|
64
|
-
// clause last rather than the abort key.
|
|
65
21
|
if (running)
|
|
66
22
|
return runningHintClauses({ glyphs, services }).join(dot);
|
|
67
|
-
// The hint names the DESTINATION, not the current state — the correct label
|
|
68
|
-
// for a toggle affordance, and the reason the mode word here is the OPPOSITE
|
|
69
|
-
// of the one on the chip beside it.
|
|
70
23
|
const toggle = `${toggleKey} ${MODE_LABEL[nextMode(mode)].toLowerCase()}`;
|
|
71
|
-
// The faded form KEEPS the mode toggle. Progressive disclosure exists to
|
|
72
|
-
// retire things the user has demonstrably learned, and `submitCount` counts
|
|
73
|
-
// submissions — a user can send fifty messages without ever discovering
|
|
74
|
-
// Shift+Tab. Same argument that keeps `esc abort` un-faded above.
|
|
75
|
-
// NEVER FADED, for the reason the props doc gives: it is the only visible way
|
|
76
|
-
// to stop a process the agent left running, and progressive disclosure retires
|
|
77
|
-
// things the user has demonstrably learned — not emergency exits.
|
|
78
24
|
const stop = services > 0 ? [`ctrl+c stop ${services}`] : [];
|
|
79
25
|
if (submitCount >= HINT_FADE_AFTER)
|
|
80
26
|
return [...stop, toggle, '? help'].join(dot);
|
|
@@ -88,31 +34,9 @@ function hintText(opts) {
|
|
|
88
34
|
'? help',
|
|
89
35
|
].join(dot);
|
|
90
36
|
}
|
|
91
|
-
/**
|
|
92
|
-
* `hintText`, for the interrupt-ladder tests (AC-43).
|
|
93
|
-
*
|
|
94
|
-
* EXPORTED UNDER A TEST-FACING NAME rather than exporting `hintText` itself, so
|
|
95
|
-
* the private helper stays private in the module graph and nothing in `src/`
|
|
96
|
-
* grows a second caller by accident. The row is the ONLY visible way to stop a
|
|
97
|
-
* process the agent left running, so it is worth asserting on directly rather
|
|
98
|
-
* than fishing the string out of a rendered frame.
|
|
99
|
-
*/
|
|
37
|
+
/** Compatibility export for existing hint contract tests. */
|
|
100
38
|
export const hintTextForTest = hintText;
|
|
101
|
-
function
|
|
102
|
-
const glyphs = pickGlyphs(caps);
|
|
103
|
-
const separator = ` ${glyphs.midDot} `;
|
|
104
|
-
const clauses = runningHintClauses({ glyphs, services });
|
|
105
|
-
const plan = planRunRow({
|
|
106
|
-
cols,
|
|
107
|
-
// Spinner glyph + the space after it, then the label.
|
|
108
|
-
labelCols: 2 + stringWidth(resolveActivityLabel(runRow.activity, glyphs)),
|
|
109
|
-
hintClauseCols: clauses.map((clause) => stringWidth(clause)),
|
|
110
|
-
separatorCols: stringWidth(separator),
|
|
111
|
-
chipCols: modeChipCols(agentMode),
|
|
112
|
-
});
|
|
113
|
-
return (_jsxs(Box, { ref: runRow.rowRef, flexDirection: "row", flexShrink: 0, children: [_jsx(Box, { width: RUN_ROW_LEAD, flexShrink: 0, children: _jsx(Text, { children: ' ' }) }), _jsx(Box, { width: plan.labelCols, flexShrink: 0, children: _jsx(ActivityLabel, { ...runRow.activity, spinnerLive: runRow.live, theme: theme, caps: caps }) }), plan.hintClauses > 0 && (_jsxs(Text, { wrap: "truncate", color: theme.hintFg ?? theme.muted, children: [separator, clauses.slice(0, plan.hintClauses).join(separator)] })), plan.chip && (_jsxs(_Fragment, { children: [_jsx(Box, { flexGrow: 1 }), _jsx(ModeChip, { mode: agentMode, theme: theme, caps: caps })] }))] }));
|
|
114
|
-
}
|
|
115
|
-
export function Composer({ runRow, 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, }) {
|
|
39
|
+
export function Composer({ deleteDisambiguated, onCompletionContextChange, cols, cursorVisible, onInteraction, measureRef, isActive, reducedMotion, onEscape, onEscapeDismiss, running, history, commands, cwd, agentMode, popupMaxRows, popupMaxHeight, onPopupRowsChange, scrolledLines = 0, onDraftRows, onNotice, theme, caps, onSubmit, onHelp, }) {
|
|
116
40
|
const [hasDraft, setHasDraft] = useState(false);
|
|
117
41
|
const onDraftChange = useCallback((next) => {
|
|
118
42
|
setHasDraft(next.hasDraft);
|
|
@@ -130,20 +54,5 @@ export function Composer({ runRow, cols, cursorVisible, onInteraction, measureRe
|
|
|
130
54
|
: hasDraft
|
|
131
55
|
? theme.focusBorder ?? theme.primary
|
|
132
56
|
: theme.idleBorder ?? theme.border;
|
|
133
|
-
|
|
134
|
-
// While the run row is up it REPLACES the hint row below the input (same row
|
|
135
|
-
// count, so starting a run never moves the layout).
|
|
136
|
-
const runRowActive = running && !!runRow;
|
|
137
|
-
const visible = showHint && hintsEnabled && !runRowActive;
|
|
138
|
-
return (_jsxs(Box, { ref: measureRef, flexDirection: "column", flexShrink: 0, children: [runRowActive && (_jsx(RunRow, { runRow: runRow, cols: cols ?? process.stdout.columns ?? 80, services: services, agentMode: agentMode, theme: theme, caps: caps })), _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, borderColor: borderColor, onDraftChange: onDraftChange, onNotice: onNotice, scrolledLines: scrolledLines }), visible && (
|
|
139
|
-
// The chip rides the EXISTING hint row rather than adding one, so it
|
|
140
|
-
// costs zero rows on a 24-row terminal.
|
|
141
|
-
_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({
|
|
142
|
-
running,
|
|
143
|
-
submitCount,
|
|
144
|
-
glyphs,
|
|
145
|
-
mode: agentMode,
|
|
146
|
-
toggleKey: modeToggleKey,
|
|
147
|
-
services,
|
|
148
|
-
}) })] }))] }));
|
|
57
|
+
return (_jsx(Box, { ref: measureRef, flexDirection: "column", flexShrink: 0, children: _jsx(PromptInput, { deleteDisambiguated: deleteDisambiguated, onCompletionContextChange: onCompletionContextChange, 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, borderColor: borderColor, onDraftChange: onDraftChange, onNotice: onNotice, scrolledLines: scrolledLines }) }));
|
|
149
58
|
}
|
package/dist/ui/PromptInput.d.ts
CHANGED
|
@@ -52,6 +52,9 @@ interface PromptInputProps {
|
|
|
52
52
|
/** Total menu rows, including borders and the overflow hint. */
|
|
53
53
|
popupMaxHeight?: number;
|
|
54
54
|
onPopupRowsChange?: (rows: number) => void;
|
|
55
|
+
onCompletionContextChange?: (context: 'none' | 'slash' | 'file') => void;
|
|
56
|
+
/** True only when the stdin filter normalized DEL before Ink parsed it. */
|
|
57
|
+
deleteDisambiguated?: boolean;
|
|
55
58
|
onSubmit: (text: string) => ComposerSubmitResult;
|
|
56
59
|
onHelp?: () => void;
|
|
57
60
|
onEscape?: () => void;
|
|
@@ -154,5 +157,5 @@ export declare function fileTokenAt(buffer: string, cursor: number): {
|
|
|
154
157
|
end: number;
|
|
155
158
|
query: string;
|
|
156
159
|
} | null;
|
|
157
|
-
export declare function PromptInput({ cols: colsProp, cursorVisible, onInteraction, isActive, running, history, commands, cwd, theme, caps, popupMaxRows, popupMaxHeight, onPopupRowsChange, agentMode, onSubmit, onHelp, onEscape, onEscapeDismiss, reducedMotion, borderColor, onDraftChange, onNotice, scrolledLines, }: PromptInputProps): React.ReactElement;
|
|
160
|
+
export declare function PromptInput({ cols: colsProp, cursorVisible, onInteraction, isActive, running, history, commands, cwd, theme, caps, popupMaxRows, popupMaxHeight, onPopupRowsChange, onCompletionContextChange, deleteDisambiguated, agentMode, onSubmit, onHelp, onEscape, onEscapeDismiss, reducedMotion, borderColor, onDraftChange, onNotice, scrolledLines, }: PromptInputProps): React.ReactElement;
|
|
158
161
|
export {};
|
package/dist/ui/PromptInput.js
CHANGED
|
@@ -34,6 +34,8 @@ import { planComposerInput } from './composer-input.js';
|
|
|
34
34
|
import { allocatePasteId, expandPastes, expandRangeOverTokens } from './paste-tokens.js';
|
|
35
35
|
import { draftMaxRows } from './composer-limits.js';
|
|
36
36
|
import { layoutComposer, splitRowAtColumn } from './composer-rows.js';
|
|
37
|
+
import { moveVisualCursor, snapGrapheme, stepGrapheme } from './editor-navigation.js';
|
|
38
|
+
import { interactionCopy } from './interaction-copy.js';
|
|
37
39
|
import { PASTE_DRAFT_MAX_BYTES, PASTE_MAX_BLOCKS, formatPasteSize, } from '../input/limits.js';
|
|
38
40
|
// ---------------------------------------------------------------------------
|
|
39
41
|
// The scroll chip (§4.2)
|
|
@@ -133,6 +135,8 @@ export function draftLimitRefusal(segments, editor) {
|
|
|
133
135
|
// ---------------------------------------------------------------------------
|
|
134
136
|
const isSpace = (ch) => !!ch && /\s/.test(ch);
|
|
135
137
|
function lineStart(buffer, cursor) {
|
|
138
|
+
if (cursor <= 0)
|
|
139
|
+
return 0;
|
|
136
140
|
const nl = buffer.lastIndexOf('\n', cursor - 1);
|
|
137
141
|
return nl === -1 ? 0 : nl + 1;
|
|
138
142
|
}
|
|
@@ -166,23 +170,23 @@ function nextWord(buffer, cursor) {
|
|
|
166
170
|
* label the user then sends INSTEAD of the text it stands for.
|
|
167
171
|
*/
|
|
168
172
|
function killRange(buffer, from, to) {
|
|
169
|
-
const range = expandRangeOverTokens(buffer, from, to);
|
|
173
|
+
const range = expandRangeOverTokens(buffer, snapGrapheme(buffer, from), snapGrapheme(buffer, to, true));
|
|
170
174
|
return { buffer: buffer.slice(0, range.from) + buffer.slice(range.to), cursor: range.from };
|
|
171
175
|
}
|
|
172
176
|
/** Apply an edit intent as a pure buffer/cursor transform. */
|
|
173
177
|
export function applyEdit(buffer, cursor, intent) {
|
|
178
|
+
cursor = snapGrapheme(buffer, cursor);
|
|
174
179
|
switch (intent) {
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
// a synthetic unit buys a correctness the user cannot notice.
|
|
180
|
+
// Navigation may enter a token label; insertion and range deletion retain
|
|
181
|
+
// the existing token snapping and expansion contracts.
|
|
178
182
|
case 'home':
|
|
179
183
|
return { buffer, cursor: lineStart(buffer, cursor) };
|
|
180
184
|
case 'end':
|
|
181
185
|
return { buffer, cursor: lineEnd(buffer, cursor) };
|
|
182
186
|
case 'wordLeft':
|
|
183
|
-
return { buffer, cursor: prevWord(buffer, cursor) };
|
|
187
|
+
return { buffer, cursor: snapGrapheme(buffer, prevWord(buffer, cursor)) };
|
|
184
188
|
case 'wordRight':
|
|
185
|
-
return { buffer, cursor: nextWord(buffer, cursor) };
|
|
189
|
+
return { buffer, cursor: snapGrapheme(buffer, nextWord(buffer, cursor), true) };
|
|
186
190
|
case 'deleteWordBack':
|
|
187
191
|
return killRange(buffer, prevWord(buffer, cursor), cursor);
|
|
188
192
|
case 'killToStart':
|
|
@@ -197,19 +201,8 @@ export function applyEdit(buffer, cursor, intent) {
|
|
|
197
201
|
* prompt-history recall.
|
|
198
202
|
*/
|
|
199
203
|
export function moveVertical(buffer, cursor, dir) {
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
return null;
|
|
203
|
-
const before = buffer.slice(0, cursor);
|
|
204
|
-
const idx = before.split('\n').length - 1;
|
|
205
|
-
const col = before.length - (before.lastIndexOf('\n') + 1);
|
|
206
|
-
const target = dir === 'up' ? idx - 1 : idx + 1;
|
|
207
|
-
if (target < 0 || target >= lines.length)
|
|
208
|
-
return null;
|
|
209
|
-
let start = 0;
|
|
210
|
-
for (let i = 0; i < target; i += 1)
|
|
211
|
-
start += lines[i].length + 1;
|
|
212
|
-
return { cursor: start + Math.min(col, lines[target].length) };
|
|
204
|
+
const moved = moveVisualCursor({ buffer, cursor, direction: dir, cols: Number.MAX_SAFE_INTEGER });
|
|
205
|
+
return moved ? { cursor: moved.cursor } : null;
|
|
213
206
|
}
|
|
214
207
|
/**
|
|
215
208
|
* Slash-command suggestions when the buffer is a bare `/word` (no space yet).
|
|
@@ -250,7 +243,7 @@ function isControlSeq(input) {
|
|
|
250
243
|
// ---------------------------------------------------------------------------
|
|
251
244
|
// Component
|
|
252
245
|
// ---------------------------------------------------------------------------
|
|
253
|
-
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, borderColor, onDraftChange, onNotice, scrolledLines = 0, }) {
|
|
246
|
+
export function PromptInput({ cols: colsProp, cursorVisible = true, onInteraction, isActive, running, history, commands, cwd, theme, caps, popupMaxRows, popupMaxHeight, onPopupRowsChange, onCompletionContextChange, deleteDisambiguated = false, agentMode, onSubmit, onHelp, onEscape, onEscapeDismiss, reducedMotion = false, borderColor, onDraftChange, onNotice, scrolledLines = 0, }) {
|
|
254
247
|
const { stdout } = useStdout();
|
|
255
248
|
const [editor, dispatch] = useReducer(editorReducer, INITIAL_EDITOR_STATE);
|
|
256
249
|
const { buffer, cursor, historyIndex, dismissed, sel } = editor;
|
|
@@ -280,6 +273,17 @@ export function PromptInput({ cols: colsProp, cursorVisible = true, onInteractio
|
|
|
280
273
|
onPopupRowsChange?.(popupLayout.rowCount);
|
|
281
274
|
}, [onPopupRowsChange, popupLayout.rowCount]);
|
|
282
275
|
useLayoutEffect(() => () => onPopupRowsChange?.(0), [onPopupRowsChange]);
|
|
276
|
+
const completion = popupVisible ? popupKind ?? 'none' : 'none';
|
|
277
|
+
const completionCallback = useRef(onCompletionContextChange);
|
|
278
|
+
completionCallback.current = onCompletionContextChange;
|
|
279
|
+
const lastCompletion = useRef();
|
|
280
|
+
useLayoutEffect(() => {
|
|
281
|
+
if (!onCompletionContextChange || lastCompletion.current === completion)
|
|
282
|
+
return;
|
|
283
|
+
lastCompletion.current = completion;
|
|
284
|
+
onCompletionContextChange?.(completion);
|
|
285
|
+
}, [onCompletionContextChange, completion]);
|
|
286
|
+
useLayoutEffect(() => () => completionCallback.current?.('none'), []);
|
|
283
287
|
// --- Debounced `@file` scan. ---------------------------------------------
|
|
284
288
|
useEffect(() => {
|
|
285
289
|
if (fileQuery === null || fileQuery.length < 1) {
|
|
@@ -347,6 +351,8 @@ export function PromptInput({ cols: colsProp, cursorVisible = true, onInteractio
|
|
|
347
351
|
const overflows = wide.hiddenAbove > 0 || wide.hiddenBelow > 0;
|
|
348
352
|
const usableCols = overflows ? Math.max(8, baseCols - OVERFLOW_CELLS - 1) : baseCols;
|
|
349
353
|
const layout = overflows ? measure(usableCols) : wide;
|
|
354
|
+
const preferredColumnWidth = useRef();
|
|
355
|
+
useLayoutEffect(() => { preferredColumnWidth.current = undefined; }, [usableCols]);
|
|
350
356
|
const draftRowCount = Math.max(1, layout.rows.length);
|
|
351
357
|
const caretResetKey = useMemo(() => ({}), [buffer, cursor, isActive, usableCols]);
|
|
352
358
|
// --- Draft presence and height, reported only on a transition. -----------
|
|
@@ -446,9 +452,12 @@ export function PromptInput({ cols: colsProp, cursorVisible = true, onInteractio
|
|
|
446
452
|
};
|
|
447
453
|
const verticalOrHistory = (dir) => {
|
|
448
454
|
onInteraction?.();
|
|
449
|
-
const moved =
|
|
455
|
+
const moved = moveVisualCursor({ buffer, cursor, cols: usableCols, direction: dir,
|
|
456
|
+
preferredVisualColumn: preferredColumnWidth.current === usableCols
|
|
457
|
+
? editor.preferredVisualColumn : undefined });
|
|
450
458
|
if (moved) {
|
|
451
|
-
|
|
459
|
+
preferredColumnWidth.current = usableCols;
|
|
460
|
+
dispatch({ type: 'moveCursor', ...moved });
|
|
452
461
|
return;
|
|
453
462
|
}
|
|
454
463
|
// At an edge: recall history only from an empty buffer or while already
|
|
@@ -514,11 +523,15 @@ export function PromptInput({ cols: colsProp, cursorVisible = true, onInteractio
|
|
|
514
523
|
return;
|
|
515
524
|
}
|
|
516
525
|
// Line-editing intents (Home/End/word/kill) recognized from raw keys.
|
|
517
|
-
const intent =
|
|
526
|
+
const intent = key.meta && (key.backspace || key.delete)
|
|
527
|
+
? 'deleteWordBack' : recognize(input, key);
|
|
518
528
|
if (intent) {
|
|
519
529
|
onInteraction?.();
|
|
520
530
|
const next = applyEdit(buffer, cursor, intent);
|
|
521
|
-
|
|
531
|
+
const moving = intent === 'home' || intent === 'end'
|
|
532
|
+
|| intent === 'wordLeft' || intent === 'wordRight';
|
|
533
|
+
dispatch(moving ? { type: 'moveCursor', cursor: next.cursor }
|
|
534
|
+
: { type: 'replace', buffer: next.buffer, cursor: next.cursor });
|
|
522
535
|
return;
|
|
523
536
|
}
|
|
524
537
|
if (key.ctrl)
|
|
@@ -539,19 +552,17 @@ export function PromptInput({ cols: colsProp, cursorVisible = true, onInteractio
|
|
|
539
552
|
// unconditional dispatch and a no-op at the left edge returns the same
|
|
540
553
|
// state object — which React bails out on, exactly as the old
|
|
541
554
|
// `if (cursor > 0)` skipped the `setState`s.
|
|
542
|
-
dispatch({ type: 'backspace' });
|
|
555
|
+
dispatch({ type: key.delete && deleteDisambiguated ? 'delete' : 'backspace' });
|
|
543
556
|
return;
|
|
544
557
|
}
|
|
545
558
|
if (key.leftArrow) {
|
|
546
559
|
onInteraction?.();
|
|
547
|
-
|
|
548
|
-
dispatch({ type: 'moveCursor', cursor: Math.max(0, cursor - step) });
|
|
560
|
+
dispatch({ type: 'moveCursor', cursor: stepGrapheme(buffer, cursor, 'left') });
|
|
549
561
|
return;
|
|
550
562
|
}
|
|
551
563
|
if (key.rightArrow) {
|
|
552
564
|
onInteraction?.();
|
|
553
|
-
|
|
554
|
-
dispatch({ type: 'moveCursor', cursor: Math.min(buffer.length, cursor + step) });
|
|
565
|
+
dispatch({ type: 'moveCursor', cursor: stepGrapheme(buffer, cursor, 'right') });
|
|
555
566
|
return;
|
|
556
567
|
}
|
|
557
568
|
// `!key.shift` is load-bearing: Shift+↑/↓ is the viewport's line-scroll
|
|
@@ -576,11 +587,7 @@ export function PromptInput({ cols: colsProp, cursorVisible = true, onInteractio
|
|
|
576
587
|
}, { isActive });
|
|
577
588
|
const marker = running ? glyphs.steer : glyphs.caret;
|
|
578
589
|
const markerColor = running ? theme.toolRunning : theme.primary;
|
|
579
|
-
const placeholder = running
|
|
580
|
-
? `Type to queue a message, Esc twice to interrupt${glyphs.ellipsis}`
|
|
581
|
-
: agentMode === 'plan'
|
|
582
|
-
? `Describe what you want to build; I'll research and plan it first${glyphs.ellipsis}`
|
|
583
|
-
: `Send a message (/ for commands, @ for files)${glyphs.ellipsis}`;
|
|
590
|
+
const placeholder = running ? interactionCopy.runningPlaceholder : interactionCopy.idlePlaceholder;
|
|
584
591
|
const overflow = overflowChip(layout.hiddenAbove, layout.hiddenBelow, glyphs);
|
|
585
592
|
const tokenColor = theme.hintFg ?? theme.muted;
|
|
586
593
|
const renderSegments = (segments, keyPrefix) => segments.map((segment, i) => (_jsx(Text, { color: segment.kind === 'token' ? tokenColor : undefined, children: segment.text }, `${keyPrefix}${i}`)));
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { QueueLayout } from './layout/queue-layout.js';
|
|
3
|
+
import type { Theme } from './theme.js';
|
|
4
|
+
/** Persistent receipt-backed footer, with no timer or separate queue state. */
|
|
5
|
+
export declare const QueuePanel: React.NamedExoticComponent<{
|
|
6
|
+
layout: QueueLayout;
|
|
7
|
+
theme: Theme;
|
|
8
|
+
}>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
/** Persistent receipt-backed footer, with no timer or separate queue state. */
|
|
5
|
+
export const QueuePanel = React.memo(function QueuePanel({ layout, theme }) {
|
|
6
|
+
if (!layout.rows)
|
|
7
|
+
return null;
|
|
8
|
+
return _jsxs(Box, { flexDirection: "column", flexShrink: 0, height: layout.rows, children: [_jsx(Text, { color: theme.accent, wrap: "truncate", children: layout.title }), layout.rows > 1 && layout.items.map((item) => _jsx(Text, { color: theme.muted, wrap: "truncate", children: item.label }, item.queueId))] });
|
|
9
|
+
});
|
|
@@ -12,6 +12,8 @@ export interface QueueStatusRowProps {
|
|
|
12
12
|
}
|
|
13
13
|
/** Return the first nonempty logical line without terminal control characters. */
|
|
14
14
|
export declare function queueSummary(text: string): string;
|
|
15
|
+
/** Truncate at a whole grapheme, reserving the ellipsis in the same cell budget. */
|
|
16
|
+
export declare function truncateSummary(text: string, columns: number, ellipsis: string): string;
|
|
15
17
|
/** Format a queue using only the columns assigned by the status bar. */
|
|
16
18
|
export declare function formatQueueStatus(props: Omit<QueueStatusRowProps, 'theme'>): {
|
|
17
19
|
prefix: string;
|
|
@@ -11,7 +11,7 @@ export function queueSummary(text) {
|
|
|
11
11
|
.find(Boolean) ?? '(empty)';
|
|
12
12
|
}
|
|
13
13
|
/** Truncate at a whole grapheme, reserving the ellipsis in the same cell budget. */
|
|
14
|
-
function truncateSummary(text, columns, ellipsis) {
|
|
14
|
+
export function truncateSummary(text, columns, ellipsis) {
|
|
15
15
|
if (stringWidth(text) <= columns)
|
|
16
16
|
return text;
|
|
17
17
|
if (columns < stringWidth(ellipsis))
|