@aragon-agent/cli 0.6.4 → 0.6.7
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 +108 -11
- package/dist/agent/controller.d.ts +39 -2
- package/dist/agent/controller.js +125 -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 +48 -1
- package/dist/commands/builtins.js +9 -0
- 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/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/ui/App.js +75 -63
- 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 +3 -1
- package/dist/ui/Composer.js +2 -2
- package/dist/ui/PromptInput.d.ts +4 -1
- package/dist/ui/PromptInput.js +19 -5
- package/dist/ui/StatusBar.d.ts +21 -15
- package/dist/ui/StatusBar.js +48 -9
- 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/UpdateLine.d.ts +1 -1
- package/dist/ui/capabilities.js +5 -5
- package/dist/ui/exit-snapshot.d.ts +9 -0
- package/dist/ui/layout/ScrollViewport.d.ts +1 -1
- package/dist/ui/layout/autocomplete.d.ts +18 -0
- package/dist/ui/layout/autocomplete.js +26 -0
- package/dist/ui/layout/rail.d.ts +4 -53
- package/dist/ui/layout/rail.js +10 -62
- 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 +24 -0
- package/dist/ui/layout/todo-layout.js +27 -0
- package/dist/ui/transcript-text.js +6 -2
- package/package.json +2 -2
package/dist/ui/TeamPanel.js
CHANGED
|
@@ -5,7 +5,7 @@ import { pickGlyphs } from './glyphs.js';
|
|
|
5
5
|
import { formatDuration } from '../agent/usage.js';
|
|
6
6
|
import { describeToolActivity, sanitizeActivity } from '../team/activity.js';
|
|
7
7
|
import { TEAM_LIMITS } from '../team/limits.js';
|
|
8
|
-
import {
|
|
8
|
+
import { buildTeamPanelLayout } from './layout/team-panel.js';
|
|
9
9
|
function isRunning(run) {
|
|
10
10
|
return run.phase === 'starting' || run.phase === 'thinking' || run.phase === 'tool' ||
|
|
11
11
|
run.phase === 'waiting';
|
|
@@ -101,24 +101,28 @@ export function activityLine(run, cols) {
|
|
|
101
101
|
return 'aborted';
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
|
-
export function TeamPanel({ snapshot, rows, cols, reducedMotion, theme, caps, now, }) {
|
|
104
|
+
export function TeamPanel({ snapshot, rows, cols, reducedMotion, theme, caps, now, layout, }) {
|
|
105
|
+
const projection = layout ?? buildTeamPanelLayout({
|
|
106
|
+
snapshot, terminalRows: rows, availableRows: Infinity,
|
|
107
|
+
});
|
|
108
|
+
if (projection.rowCount === 0)
|
|
109
|
+
return null;
|
|
105
110
|
const glyphs = pickGlyphs(caps);
|
|
106
111
|
const at = now ?? Date.now();
|
|
107
112
|
const running = snapshot.runs.filter(isRunning).length;
|
|
108
113
|
const done = snapshot.runs.filter(isSettled).length;
|
|
109
114
|
const elapsed = formatDuration(Math.max(0, at - snapshot.startedAt));
|
|
110
115
|
const header = (_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", children: [_jsxs(Text, { wrap: "truncate", color: theme.accent, bold: true, children: [glyphs.teamAgent, " team", ' ', _jsxs(Text, { color: theme.muted, children: [running, " running ", glyphs.midDot, " ", done, " done ", glyphs.midDot, " ", elapsed] })] }), snapshot.messageCount > 0 && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [glyphs.teamMail, " ", snapshot.messageCount] }))] }));
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
if (rows < TEAM_LIMITS.panelCollapseRows) {
|
|
116
|
+
// Short terminals or a long draft leave only the shared header projection,
|
|
117
|
+
// which still answers whether anything is running and for how long.
|
|
118
|
+
if (projection.collapsed) {
|
|
115
119
|
return _jsx(Box, { flexDirection: "column", flexShrink: 0, children: header });
|
|
116
120
|
}
|
|
117
121
|
// RANKED, not sliced by array index (F-2). The slot pool hands out indices in
|
|
118
122
|
// order, so `slice(0, 5)` showed the five children that finished FIRST and hid
|
|
119
123
|
// everything still working behind `+N more` - at exactly the fan-out widths
|
|
120
124
|
// the requirement permits.
|
|
121
|
-
const { visible, hiddenTotal, hiddenRunning } =
|
|
125
|
+
const { visible, hiddenTotal, hiddenRunning } = projection;
|
|
122
126
|
return (_jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [header, visible.map((run) => {
|
|
123
127
|
const color = statusColor(run, theme);
|
|
124
128
|
// One spinner per RUNNING row, and the row cap is 5 precisely because
|
|
@@ -126,8 +130,8 @@ export function TeamPanel({ snapshot, rows, cols, reducedMotion, theme, caps, no
|
|
|
126
130
|
// `reducedMotion` and a non-Unicode terminal fall back to the same
|
|
127
131
|
// static marker, exactly as `AssistantEntry` and `ToolCard` already do.
|
|
128
132
|
const animate = isRunning(run) && !reducedMotion && caps.unicode;
|
|
129
|
-
return (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: color, children: [' ', animate ? _jsx(Spinner, { type: "dots" }) : marker(run, glyphs), ' '] }), _jsx(Text, { color: theme.primary, children: `${run.label}${run.tier === 'fast' ? '~' : ''}`.padEnd(8).slice(0, 8) }), _jsx(Box, { flexGrow: 1, flexShrink: 1, overflow: "hidden", children: _jsx(Text, { wrap: "truncate", color: theme.muted, children: run.description }) }), _jsx(Box, { flexShrink: 1, overflow: "hidden", children: _jsxs(Text, { wrap: "truncate", color: color, children: [' ', activityLine(run, cols)] }) }), _jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', run.startedAt ? formatDuration(Math.max(0, (run.endedAt ?? at) - run.startedAt)) : ''] })] }, run.label));
|
|
130
|
-
}), hiddenTotal > 0 && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', "+", hiddenTotal, " more", hiddenRunning > 0 ? ` (${hiddenRunning} running)` : ''] })), snapshot.lastMessage && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', glyphs.teamMail, " ", snapshot.lastMessage.from, " ", glyphs.arrowRight, " ", snapshot.lastMessage.to, ": ", snapshot.lastMessage.subject] }))] }));
|
|
133
|
+
return (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: color, children: [' ', animate ? _jsx(Spinner, { type: "dots" }) : marker(run, glyphs), ' '] }), _jsx(Text, { color: theme.primary, children: `${run.label}${run.tier === 'fast' ? '~' : ''}`.padEnd(8).slice(0, 8) }), _jsx(Box, { flexGrow: 1, flexShrink: 1, overflow: "hidden", children: _jsx(Text, { wrap: "truncate", color: theme.muted, children: run.description.replace(/\s+/g, ' ') }) }), _jsx(Box, { flexShrink: 1, overflow: "hidden", children: _jsxs(Text, { wrap: "truncate", color: color, children: [' ', activityLine(run, cols)] }) }), _jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', run.startedAt ? formatDuration(Math.max(0, (run.endedAt ?? at) - run.startedAt)) : ''] })] }, run.label));
|
|
134
|
+
}), hiddenTotal > 0 && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', "+", hiddenTotal, " more", hiddenRunning > 0 ? ` (${hiddenRunning} running)` : ''] })), snapshot.lastMessage && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', glyphs.teamMail, " ", snapshot.lastMessage.from, " ", glyphs.arrowRight, " ", snapshot.lastMessage.to, ": ", snapshot.lastMessage.subject.replace(/\s+/g, ' ')] }))] }));
|
|
131
135
|
}
|
|
132
136
|
function marker(run, glyphs) {
|
|
133
137
|
if (run.phase === 'done')
|
package/dist/ui/TodoPanel.d.ts
CHANGED
|
@@ -14,13 +14,13 @@
|
|
|
14
14
|
* stop being a rail and start eating viewport height, growing and shrinking the
|
|
15
15
|
* transcript every time the list changed length.
|
|
16
16
|
*
|
|
17
|
-
* WIDTH IS MEASURED IN COLUMNS, NEVER IN CODE UNITS (P2-3). This rail is
|
|
17
|
+
* WIDTH IS MEASURED IN COLUMNS, NEVER IN CODE UNITS (P2-3). This rail is 14-36
|
|
18
18
|
* columns wide and the system prompt's last line is "Respond in the language the
|
|
19
19
|
* user writes in", so CJK item text in an 18-column rail is the NORMAL case
|
|
20
20
|
* rather than the edge case. Every text cell is `wrap="truncate"` and Ink's
|
|
21
21
|
* truncation is display-width aware, so nothing can overflow the box — and NO
|
|
22
|
-
* ARITHMETIC HERE MAY BUDGET USER TEXT BY `.length`. The
|
|
23
|
-
* `padStart(2)` cells below are ASCII
|
|
22
|
+
* ARITHMETIC HERE MAY BUDGET USER TEXT BY `.length`. The count string and
|
|
23
|
+
* `padStart(2)` cells below are ASCII counts and indices, which is why they are
|
|
24
24
|
* safe; a "characters that fit" calculation over `content` would not be. That is
|
|
25
25
|
* the whole of the lesson `TeamPanel`'s `WIDE_CHAR_ALLOWANCE` records at length,
|
|
26
26
|
* and the mitigation here is to have no such budget at all.
|
|
@@ -34,7 +34,7 @@ export interface TodoPanelProps {
|
|
|
34
34
|
/** From `todoRailWidth(cols)`. The rail's whole column budget, separator included. */
|
|
35
35
|
width: number;
|
|
36
36
|
/**
|
|
37
|
-
* From `
|
|
37
|
+
* From `buildTodoRailLayout` after actual team/menu rows are subtracted.
|
|
38
38
|
* (I-10 / P1-3). The gate that mounts this component and the number it lays
|
|
39
39
|
* out against are ONE value computed once in `App`: a panel that mounts on one
|
|
40
40
|
* number and lays out against another renders nothing and reports nothing.
|
|
@@ -46,4 +46,4 @@ export interface TodoPanelProps {
|
|
|
46
46
|
theme: Theme;
|
|
47
47
|
caps: TermCapabilities;
|
|
48
48
|
}
|
|
49
|
-
export declare function TodoPanel({ snapshot, width, rows, running, reducedMotion, theme, caps, }: TodoPanelProps): React.ReactElement;
|
|
49
|
+
export declare function TodoPanel({ snapshot, width, rows, running, reducedMotion, theme, caps, }: TodoPanelProps): React.ReactElement | null;
|
package/dist/ui/TodoPanel.js
CHANGED
|
@@ -1,4 +1,32 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* TodoPanel — the right rail (todo-plan-execution §6.1).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY: every glyph comes from `pickGlyphs`; a literal here bypasses the
|
|
6
|
+
* capability probe and shows mojibake on a legacy console (`glyphs.ts` header).
|
|
7
|
+
*
|
|
8
|
+
* MOUNTED ONLY WHEN A LIST EXISTS. `App` passes `null` the rest of the time and
|
|
9
|
+
* this component is not rendered at all, so a session that never plans has zero
|
|
10
|
+
* columns and zero layout shift (R-g's "no furniture when idle" / AC-24).
|
|
11
|
+
*
|
|
12
|
+
* IT MUST BE MOUNTED IN `AppShell`'s MIDDLE BAND, NEVER IN THE BOTTOM CHROME
|
|
13
|
+
* (C-6 / I-4). This is a COLUMN BESIDE the viewport, and the middle band is the
|
|
14
|
+
* only row-wrapped region there is; moved down into the bottom chrome it would
|
|
15
|
+
* stop being a rail and start eating viewport height, growing and shrinking the
|
|
16
|
+
* transcript every time the list changed length.
|
|
17
|
+
*
|
|
18
|
+
* WIDTH IS MEASURED IN COLUMNS, NEVER IN CODE UNITS (P2-3). This rail is 14-36
|
|
19
|
+
* columns wide and the system prompt's last line is "Respond in the language the
|
|
20
|
+
* user writes in", so CJK item text in an 18-column rail is the NORMAL case
|
|
21
|
+
* rather than the edge case. Every text cell is `wrap="truncate"` and Ink's
|
|
22
|
+
* truncation is display-width aware, so nothing can overflow the box — and NO
|
|
23
|
+
* ARITHMETIC HERE MAY BUDGET USER TEXT BY `.length`. The count string and
|
|
24
|
+
* `padStart(2)` cells below are ASCII counts and indices, which is why they are
|
|
25
|
+
* safe; a "characters that fit" calculation over `content` would not be. That is
|
|
26
|
+
* the whole of the lesson `TeamPanel`'s `WIDE_CHAR_ALLOWANCE` records at length,
|
|
27
|
+
* and the mitigation here is to have no such budget at all.
|
|
28
|
+
*/
|
|
29
|
+
import { useMemo } from 'react';
|
|
2
30
|
import { Box, Text } from 'ink';
|
|
3
31
|
import Spinner from 'ink-spinner';
|
|
4
32
|
import { pickGlyphs } from './glyphs.js';
|
|
@@ -6,7 +34,7 @@ import { buildGauge } from './gauge.js';
|
|
|
6
34
|
import { railBorderProps } from './layout/Gutter.js';
|
|
7
35
|
import { TODO_RAIL_INDEX_MIN_COLS } from './layout/rail.js';
|
|
8
36
|
import { TODO_LIMITS } from '../todo/limits.js';
|
|
9
|
-
import { selectTodoRows } from '../todo/panel-rows.js';
|
|
37
|
+
import { selectCompactTodoRows, selectTodoRows } from '../todo/panel-rows.js';
|
|
10
38
|
/** Header row + the blank row under the gauge. */
|
|
11
39
|
const FIXED_ROWS = 2;
|
|
12
40
|
/** The gauge is the one purely decorative element, so it is the first to go. */
|
|
@@ -19,8 +47,11 @@ const MARKER_COLS = 3;
|
|
|
19
47
|
/** Index cell: two digits plus the space that separates it from the text. */
|
|
20
48
|
const INDEX_COLS = 3;
|
|
21
49
|
function marker(item, glyphs) {
|
|
22
|
-
|
|
23
|
-
|
|
50
|
+
// Ink's tokenizer otherwise treats the bare emoji-capable check as one cell,
|
|
51
|
+
// while string-width counts two, adding an extra visible space to each row.
|
|
52
|
+
if (item.status === 'completed') {
|
|
53
|
+
return glyphs.todoDone.length === 1 ? `${glyphs.todoDone}\uFE0F` : glyphs.todoDone;
|
|
54
|
+
}
|
|
24
55
|
if (item.status === 'in_progress')
|
|
25
56
|
return glyphs.todoActive;
|
|
26
57
|
return glyphs.todoPending;
|
|
@@ -34,41 +65,42 @@ function rowColor(item, theme) {
|
|
|
34
65
|
}
|
|
35
66
|
export function TodoPanel({ snapshot, width, rows, running, reducedMotion, theme, caps, }) {
|
|
36
67
|
const glyphs = pickGlyphs(caps);
|
|
68
|
+
// Ink reapplies all four Yoga borders when the borderStyle object changes,
|
|
69
|
+
// but unchanged borderTop/Bottom=false props are not reapplied on that update.
|
|
70
|
+
const border = useMemo(() => railBorderProps(glyphs.railVertical), [glyphs.railVertical]);
|
|
71
|
+
if (snapshot.items.length === 0 || rows < TODO_LIMITS.panelMinRows)
|
|
72
|
+
return null;
|
|
73
|
+
const compact = rows < TODO_LIMITS.panelFullRows;
|
|
37
74
|
const showGauge = width >= GAUGE_MIN_COLS && rows >= GAUGE_MIN_ROWS;
|
|
38
|
-
const showIndex = width >= TODO_RAIL_INDEX_MIN_COLS;
|
|
39
|
-
const
|
|
40
|
-
const
|
|
75
|
+
const showIndex = !compact && width >= TODO_RAIL_INDEX_MIN_COLS;
|
|
76
|
+
const compactFooter = compact && snapshot.items.length > rows - 1;
|
|
77
|
+
const itemRows = compact ? rows - 1 - Number(compactFooter)
|
|
78
|
+
: rows - FIXED_ROWS - Number(showGauge);
|
|
79
|
+
const { visible, hiddenAbove, hiddenBelow } = compact
|
|
80
|
+
? selectCompactTodoRows(snapshot.items, itemRows)
|
|
81
|
+
: selectTodoRows(snapshot.items, itemRows);
|
|
41
82
|
const pct = snapshot.total > 0 ? Math.round((snapshot.doneCount / snapshot.total) * 100) : 0;
|
|
42
83
|
const gauge = buildGauge(pct, Math.max(4, width - CHROME_COLS - 2), theme, caps);
|
|
43
84
|
const complete = snapshot.doneCount === snapshot.total;
|
|
85
|
+
const countText = `${snapshot.doneCount}/${snapshot.total}`;
|
|
86
|
+
const showDone = complete
|
|
87
|
+
&& width - CHROME_COLS >= 'TODO '.length + countText.length + ' done'.length;
|
|
88
|
+
const count = `${countText}${showDone ? ' done' : ''}`;
|
|
44
89
|
return (
|
|
45
90
|
// `flexShrink={0}` IS LOAD-BEARING (P1-4). Ink's `Box.defaultProps` is
|
|
46
91
|
// `{ flexWrap:'nowrap', flexDirection:'row', flexGrow:0, flexShrink:1 }`, so
|
|
47
92
|
// a `width={n}` box inside a shrinking row is SQUEEZED under pressure — and
|
|
48
93
|
// every piece of arithmetic in `rail.ts` silently becomes a suggestion at
|
|
49
94
|
// exactly the narrow widths where the guarantee matters. `ScrollIndicator`
|
|
50
|
-
// spells the same pair one level down.
|
|
51
|
-
//
|
|
52
|
-
_jsxs(Box, { flexDirection: "column", flexShrink: 0, width: width, overflow: "hidden", paddingLeft: 1, ...
|
|
95
|
+
// spells the same pair one level down. Height uses the same row budget as
|
|
96
|
+
// selection, so content cannot silently enlarge the middle band.
|
|
97
|
+
_jsxs(Box, { flexDirection: "column", flexShrink: 0, width: width, height: rows, overflow: "hidden", paddingLeft: 1, ...border, borderColor: theme.border, children: [_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", children: [_jsx(Box, { flexGrow: 1, flexShrink: 1, overflow: "hidden", children: _jsx(Text, { wrap: "truncate", color: theme.accent, bold: true, children: "TODO" }) }), _jsx(Box, { width: count.length, flexShrink: 0, children: _jsx(Text, { wrap: "truncate", color: complete ? theme.toolDone : theme.muted, children: count }) })] }), showGauge && (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: gauge.fillColor, children: gauge.filled }), _jsx(Text, { color: gauge.trackColor, children: gauge.empty })] })), !compact && _jsx(Text, { children: " " }), !compact && hiddenAbove > 0 && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', "+", hiddenAbove, " above"] })), visible.map(({ item, index }) => {
|
|
53
98
|
const active = item.status === 'in_progress';
|
|
54
99
|
// The spinner is gated on `running`: a spinner while the agent is idle
|
|
55
100
|
// is a lie about the state of the world, and this panel outlives the run
|
|
56
101
|
// that filled it. Reduced motion and a non-Unicode terminal fall back to
|
|
57
102
|
// the same static marker, exactly as `AssistantEntry` and `TeamPanel` do.
|
|
58
103
|
const animate = active && running && !reducedMotion && caps.unicode;
|
|
59
|
-
return (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { flexShrink: 0, width: MARKER_COLS, children: _jsx(Text, { color: rowColor(item, theme), bold: active, children: animate ? _jsx(Spinner, { type: "dots" }) : marker(item, glyphs) }) }), showIndex && (_jsx(Box, { flexShrink: 0, width: INDEX_COLS, children: _jsx(Text, { color: theme.muted, children: String(index + 1).padStart(2) }) })), _jsx(Box, { flexGrow: 1, flexShrink: 1, overflow: "hidden", children: _jsx(Text, { wrap:
|
|
60
|
-
}), hiddenBelow > 0 && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', "+", hiddenBelow, " more"] }))] }));
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Keep the wrapping row inside `TODO_LIMITS.activeWrapRows`.
|
|
64
|
-
*
|
|
65
|
-
* A CHARACTER BUDGET IS NOT A COLUMN BUDGET (P2-3), so this is deliberately
|
|
66
|
-
* generous rather than exact: it exists to stop a maximum-length `activeForm`
|
|
67
|
-
* from claiming five rows of an 18-column rail, and Ink's display-width-aware
|
|
68
|
-
* truncation is what actually keeps the row inside the box. Erring long costs
|
|
69
|
-
* nothing — the surrounding `overflow="hidden"` clips it.
|
|
70
|
-
*/
|
|
71
|
-
function clampWrapped(text, width) {
|
|
72
|
-
const budget = Math.max(1, width - CHROME_COLS) * TODO_LIMITS.activeWrapRows;
|
|
73
|
-
return text.length > budget ? text.slice(0, budget) : text;
|
|
104
|
+
return (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { flexShrink: 0, width: MARKER_COLS, children: _jsx(Text, { color: rowColor(item, theme), bold: active, children: animate ? _jsx(Spinner, { type: "dots" }) : marker(item, glyphs) }) }), showIndex && (_jsx(Box, { flexShrink: 0, width: INDEX_COLS, children: _jsx(Text, { color: theme.muted, children: String(index + 1).padStart(2) }) })), _jsx(Box, { flexGrow: 1, flexShrink: 1, overflow: "hidden", children: _jsx(Text, { wrap: "truncate", color: rowColor(item, theme), bold: active, children: active ? item.activeForm : item.content }) })] }, `${index}-${item.content}`));
|
|
105
|
+
}), compactFooter && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: ["-", hiddenAbove, " +", hiddenBelow] })), !compact && hiddenBelow > 0 && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', "+", hiddenBelow, " more"] }))] }, glyphs.railVertical));
|
|
74
106
|
}
|
package/dist/ui/UpdateLine.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* still truthy. Taking that branch would emit a box of ZERO rows - the frame
|
|
10
10
|
* keeps its fixed height, Yoga hands the row to the transcript, and
|
|
11
11
|
* `viewportRows()` goes on reporting the old number to `ScrollViewport`,
|
|
12
|
-
* `selectWindow`, `overlayMaxRows`, `popupMaxRows` and `
|
|
12
|
+
* `selectWindow`, `overlayMaxRows`, `popupMaxRows` and `buildTodoRailLayout`. The
|
|
13
13
|
* transcript would gain a row while the updater is idle and lose it the moment
|
|
14
14
|
* it has news. Presence is the CALLER's decision, through
|
|
15
15
|
* `shouldRenderUpdateLine`, exactly as `App.tsx` already decides the activity
|
package/dist/ui/capabilities.js
CHANGED
|
@@ -15,10 +15,6 @@ function detectColorLevel(env) {
|
|
|
15
15
|
return 0;
|
|
16
16
|
if (env.TERM === 'dumb')
|
|
17
17
|
return 0;
|
|
18
|
-
// A truecolor-capable terminal advertises itself.
|
|
19
|
-
const colorterm = env.COLORTERM?.toLowerCase();
|
|
20
|
-
if (colorterm === 'truecolor' || colorterm === '24bit')
|
|
21
|
-
return 3;
|
|
22
18
|
// FORCE_COLOR is an explicit override of the auto-detected depth.
|
|
23
19
|
const force = env.FORCE_COLOR;
|
|
24
20
|
if (force !== undefined) {
|
|
@@ -30,6 +26,10 @@ function detectColorLevel(env) {
|
|
|
30
26
|
return 3;
|
|
31
27
|
return 1; // '1' | 'true' | '' → at least basic color
|
|
32
28
|
}
|
|
29
|
+
// A truecolor-capable terminal advertises itself.
|
|
30
|
+
const colorterm = env.COLORTERM?.toLowerCase();
|
|
31
|
+
if (colorterm === 'truecolor' || colorterm === '24bit')
|
|
32
|
+
return 3;
|
|
33
33
|
// Fall back to chalk's own platform detection.
|
|
34
34
|
const level = chalk.level;
|
|
35
35
|
return level >= 0 && level <= 3 ? level : 0;
|
|
@@ -42,7 +42,7 @@ function detectUnicode(env) {
|
|
|
42
42
|
if (env.WT_SESSION)
|
|
43
43
|
return true; // Windows Terminal
|
|
44
44
|
const program = env.TERM_PROGRAM;
|
|
45
|
-
if (program === 'vscode' || program === 'iTerm.app' || program === 'Apple_Terminal')
|
|
45
|
+
if (program === 'aragonmesh' || program === 'vscode' || program === 'iTerm.app' || program === 'Apple_Terminal')
|
|
46
46
|
return true;
|
|
47
47
|
// Legacy cmd.exe (no TERM, no WT_SESSION) and unknown terminals fall back to ASCII.
|
|
48
48
|
return false;
|
|
@@ -14,6 +14,15 @@
|
|
|
14
14
|
import type { Entry, UsageTotal } from '../agent/reducer.js';
|
|
15
15
|
export interface ExitSnapshot {
|
|
16
16
|
entries: Entry[];
|
|
17
|
+
/**
|
|
18
|
+
* The session total, FORWARDED not copied.
|
|
19
|
+
*
|
|
20
|
+
* It gained `cacheReadTokens` / `cacheWriteTokens`
|
|
21
|
+
* (context-usage-gauge-accuracy §3.6) and this file needed no change for it,
|
|
22
|
+
* which is the point of referencing the reducer's type rather than restating
|
|
23
|
+
* its fields: `renderTranscriptText` reads the total through `promptTokensOf`,
|
|
24
|
+
* so the exit summary and the status bar cannot drift apart.
|
|
25
|
+
*/
|
|
17
26
|
usageTotal: UsageTotal;
|
|
18
27
|
provider: string;
|
|
19
28
|
model: string;
|
|
@@ -76,7 +76,7 @@ export interface ScrollViewportProps {
|
|
|
76
76
|
* This component is the ONLY one inside the viewport subtree that reads
|
|
77
77
|
* `stdout.columns` directly, and the place it uses the number — the
|
|
78
78
|
* `MIN_INDICATOR_COLS` gate — is about how much room THIS BOX has, not how
|
|
79
|
-
* wide the terminal is. Once the todo rail takes
|
|
79
|
+
* wide the terminal is. Once the todo rail takes its responsive share of the frame those two
|
|
80
80
|
* stop being the same number. Optional so every existing caller (and every
|
|
81
81
|
* test) is unchanged.
|
|
82
82
|
*/
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface AutocompleteLayoutInput {
|
|
2
|
+
itemCount: number;
|
|
3
|
+
selected: number;
|
|
4
|
+
maxRows?: number;
|
|
5
|
+
maxHeight?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface AutocompleteLayout {
|
|
8
|
+
start: number;
|
|
9
|
+
count: number;
|
|
10
|
+
moreBelow: number;
|
|
11
|
+
showMore: boolean;
|
|
12
|
+
rowCount: number;
|
|
13
|
+
}
|
|
14
|
+
/** Window suggestions within a total row budget including borders and footer.
|
|
15
|
+
* Only omitted maxHeight is unlimited. Explicit invalid numbers become zero.
|
|
16
|
+
* Selection is clamped to the available items; no input is mutated or rejected.
|
|
17
|
+
*/
|
|
18
|
+
export declare function buildAutocompleteLayout(input: AutocompleteLayoutInput): AutocompleteLayout;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Window suggestions within a total row budget including borders and footer.
|
|
2
|
+
* Only omitted maxHeight is unlimited. Explicit invalid numbers become zero.
|
|
3
|
+
* Selection is clamped to the available items; no input is mutated or rejected.
|
|
4
|
+
*/
|
|
5
|
+
export function buildAutocompleteLayout(input) {
|
|
6
|
+
const total = nonNegativeInt(input.itemCount);
|
|
7
|
+
const maxRows = input.maxRows === undefined ? 6 : nonNegativeInt(input.maxRows);
|
|
8
|
+
const height = input.maxHeight === undefined ? Infinity : nonNegativeInt(input.maxHeight);
|
|
9
|
+
if (total === 0 || maxRows === 0 || height < 3) {
|
|
10
|
+
return { start: 0, count: 0, moreBelow: 0, showMore: false, rowCount: 0 };
|
|
11
|
+
}
|
|
12
|
+
const selected = Math.min(total - 1, nonNegativeInt(input.selected));
|
|
13
|
+
let count = Math.min(total, maxRows, height - 2);
|
|
14
|
+
const windowStart = () => Math.max(0, Math.min(selected - count + 1, total - count));
|
|
15
|
+
let start = windowStart();
|
|
16
|
+
if (count >= 2 && start + count < total && count + 3 > height) {
|
|
17
|
+
count--;
|
|
18
|
+
start = windowStart();
|
|
19
|
+
}
|
|
20
|
+
const moreBelow = total - start - count;
|
|
21
|
+
const showMore = moreBelow > 0 && count + 3 <= height;
|
|
22
|
+
return { start, count, moreBelow, showMore, rowCount: 2 + count + Number(showMore) };
|
|
23
|
+
}
|
|
24
|
+
function nonNegativeInt(value) {
|
|
25
|
+
return Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
|
|
26
|
+
}
|
package/dist/ui/layout/rail.d.ts
CHANGED
|
@@ -1,56 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
* Rail geometry (todo-plan-execution §3.9) — the ONE place that says how wide
|
|
3
|
-
* and how tall the todo rail is allowed to be.
|
|
4
|
-
*
|
|
5
|
-
* Pure and React-free, mirroring `budget.ts`, so `todo-rail.test.ts` can pin the
|
|
6
|
-
* arithmetic without mounting anything. Deliberately NOT part of `budget.ts`:
|
|
7
|
-
* that module answers "how many ROWS does the viewport get" and stays a function
|
|
8
|
-
* of `rows` alone; the rail spends COLUMNS.
|
|
9
|
-
*
|
|
10
|
-
* NAMED FOR ITS POSITION, NOT ITS CONTENT, like `AppShellProps.rail` (D-18):
|
|
11
|
-
* this is a layout primitive and `viewportRows` is its neighbour, not
|
|
12
|
-
* `TodoPanel`.
|
|
13
|
-
*/
|
|
14
|
-
/** Below this the marker + a two-word title do not fit and the column is noise. */
|
|
15
|
-
export declare const TODO_RAIL_MIN_COLS = 18;
|
|
16
|
-
/**
|
|
17
|
-
* Above this, 20% is being spent on whitespace: at 36 columns a step title
|
|
18
|
-
* already fits on one line, and the transcript is the surface that benefits from
|
|
19
|
-
* a 200-column terminal. A deliberate, revertible deviation from a literal
|
|
20
|
-
* reading of R-e; it only binds above 180 columns.
|
|
21
|
-
*/
|
|
1
|
+
export declare const TODO_RAIL_MIN_COLS = 14;
|
|
22
2
|
export declare const TODO_RAIL_MAX_COLS = 36;
|
|
23
|
-
|
|
24
|
-
export declare const TODO_RAIL_MIN_TOTAL_COLS
|
|
25
|
-
/** Below this rail width the 2-column index prefix is dropped for content. */
|
|
3
|
+
export declare const TODO_TRANSCRIPT_MIN_COLS = 62;
|
|
4
|
+
export declare const TODO_RAIL_MIN_TOTAL_COLS: number;
|
|
26
5
|
export declare const TODO_RAIL_INDEX_MIN_COLS = 22;
|
|
27
|
-
/**
|
|
28
|
-
* Two properties are pinned by tests, for the reason `budget.ts` records about
|
|
29
|
-
* the viewport being non-monotonic before anyone measured it:
|
|
30
|
-
*
|
|
31
|
-
* - NON-DECREASING in `cols` across [40, 300]. A user dragging a window wider
|
|
32
|
-
* must never see the rail get narrower.
|
|
33
|
-
* - `cols - todoRailWidth(cols) >= 62` for every `cols >= 80`. Every existing
|
|
34
|
-
* width heuristic inside the viewport (`TERSE_HINT_COLS` 42,
|
|
35
|
-
* `MIN_INDICATOR_COLS` 50, the status bar's 60/72 steps) sits below that
|
|
36
|
-
* floor, so no existing behaviour changes because a rail appeared (R-3).
|
|
37
|
-
*/
|
|
6
|
+
/** Return a non-decreasing rail width; invalid or narrow terminals have no rail. */
|
|
38
7
|
export declare function todoRailWidth(cols: number): number;
|
|
39
|
-
/**
|
|
40
|
-
* How many rows the rail may actually draw.
|
|
41
|
-
*
|
|
42
|
-
* IT IS NOT `viewportBudget`, AND THE DIFFERENCE IS NOT COSMETIC (P1-3 / D-23).
|
|
43
|
-
* `viewportRows()` subtracts the STATIC chrome only, and says so; the live team
|
|
44
|
-
* roster is mounted inside `AppShell`'s measured bottom box, where it costs a
|
|
45
|
-
* header row, up to `TEAM_LIMITS.panelMaxRows` child rows, a `+N more` row and a
|
|
46
|
-
* mail row that `viewportBudget` knows nothing about. `ScrollViewport` is immune
|
|
47
|
-
* because it MEASURES itself; the rail does not measure - a second
|
|
48
|
-
* measure -> setState -> measure loop is exactly what §1.2 refuses - so it has
|
|
49
|
-
* to subtract.
|
|
50
|
-
*
|
|
51
|
-
* Ink clips overflow from the BOTTOM, so an over-request does not error: it
|
|
52
|
-
* silently eats the last row, which is the `+N below` marker - the one row whose
|
|
53
|
-
* absence is indistinguishable from "the list is short". That is why this is
|
|
54
|
-
* arithmetic rather than a shrug at `overflow: hidden`.
|
|
55
|
-
*/
|
|
56
|
-
export declare function todoRailRows(viewportBudget: number, teamActive: boolean): number;
|
package/dist/ui/layout/rail.js
CHANGED
|
@@ -1,68 +1,16 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*
|
|
5
|
-
* Pure and React-free, mirroring `budget.ts`, so `todo-rail.test.ts` can pin the
|
|
6
|
-
* arithmetic without mounting anything. Deliberately NOT part of `budget.ts`:
|
|
7
|
-
* that module answers "how many ROWS does the viewport get" and stays a function
|
|
8
|
-
* of `rows` alone; the rail spends COLUMNS.
|
|
9
|
-
*
|
|
10
|
-
* NAMED FOR ITS POSITION, NOT ITS CONTENT, like `AppShellProps.rail` (D-18):
|
|
11
|
-
* this is a layout primitive and `viewportRows` is its neighbour, not
|
|
12
|
-
* `TodoPanel`.
|
|
13
|
-
*/
|
|
14
|
-
import { TODO_LIMITS } from '../../todo/limits.js';
|
|
15
|
-
/** R-e, literally: the rail asks for a fifth of the terminal. */
|
|
16
|
-
const RAIL_FRACTION = 0.2;
|
|
17
|
-
/** Below this the marker + a two-word title do not fit and the column is noise. */
|
|
18
|
-
export const TODO_RAIL_MIN_COLS = 18;
|
|
19
|
-
/**
|
|
20
|
-
* Above this, 20% is being spent on whitespace: at 36 columns a step title
|
|
21
|
-
* already fits on one line, and the transcript is the surface that benefits from
|
|
22
|
-
* a 200-column terminal. A deliberate, revertible deviation from a literal
|
|
23
|
-
* reading of R-e; it only binds above 180 columns.
|
|
24
|
-
*/
|
|
1
|
+
/** Responsive TODO rail widths, measured in terminal columns including chrome. */
|
|
2
|
+
const RAIL_FRACTION = 0.15;
|
|
3
|
+
export const TODO_RAIL_MIN_COLS = 14;
|
|
25
4
|
export const TODO_RAIL_MAX_COLS = 36;
|
|
26
|
-
|
|
27
|
-
export const TODO_RAIL_MIN_TOTAL_COLS =
|
|
28
|
-
/** Below this rail width the 2-column index prefix is dropped for content. */
|
|
5
|
+
export const TODO_TRANSCRIPT_MIN_COLS = 62;
|
|
6
|
+
export const TODO_RAIL_MIN_TOTAL_COLS = TODO_TRANSCRIPT_MIN_COLS + TODO_RAIL_MIN_COLS;
|
|
29
7
|
export const TODO_RAIL_INDEX_MIN_COLS = 22;
|
|
30
|
-
/**
|
|
31
|
-
* Two properties are pinned by tests, for the reason `budget.ts` records about
|
|
32
|
-
* the viewport being non-monotonic before anyone measured it:
|
|
33
|
-
*
|
|
34
|
-
* - NON-DECREASING in `cols` across [40, 300]. A user dragging a window wider
|
|
35
|
-
* must never see the rail get narrower.
|
|
36
|
-
* - `cols - todoRailWidth(cols) >= 62` for every `cols >= 80`. Every existing
|
|
37
|
-
* width heuristic inside the viewport (`TERSE_HINT_COLS` 42,
|
|
38
|
-
* `MIN_INDICATOR_COLS` 50, the status bar's 60/72 steps) sits below that
|
|
39
|
-
* floor, so no existing behaviour changes because a rail appeared (R-3).
|
|
40
|
-
*/
|
|
8
|
+
/** Return a non-decreasing rail width; invalid or narrow terminals have no rail. */
|
|
41
9
|
export function todoRailWidth(cols) {
|
|
42
|
-
if (!Number.isFinite(cols)
|
|
10
|
+
if (!Number.isFinite(cols))
|
|
43
11
|
return 0;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* How many rows the rail may actually draw.
|
|
48
|
-
*
|
|
49
|
-
* IT IS NOT `viewportBudget`, AND THE DIFFERENCE IS NOT COSMETIC (P1-3 / D-23).
|
|
50
|
-
* `viewportRows()` subtracts the STATIC chrome only, and says so; the live team
|
|
51
|
-
* roster is mounted inside `AppShell`'s measured bottom box, where it costs a
|
|
52
|
-
* header row, up to `TEAM_LIMITS.panelMaxRows` child rows, a `+N more` row and a
|
|
53
|
-
* mail row that `viewportBudget` knows nothing about. `ScrollViewport` is immune
|
|
54
|
-
* because it MEASURES itself; the rail does not measure - a second
|
|
55
|
-
* measure -> setState -> measure loop is exactly what §1.2 refuses - so it has
|
|
56
|
-
* to subtract.
|
|
57
|
-
*
|
|
58
|
-
* Ink clips overflow from the BOTTOM, so an over-request does not error: it
|
|
59
|
-
* silently eats the last row, which is the `+N below` marker - the one row whose
|
|
60
|
-
* absence is indistinguishable from "the list is short". That is why this is
|
|
61
|
-
* arithmetic rather than a shrug at `overflow: hidden`.
|
|
62
|
-
*/
|
|
63
|
-
export function todoRailRows(viewportBudget, teamActive) {
|
|
64
|
-
if (!Number.isFinite(viewportBudget))
|
|
12
|
+
const columns = Math.floor(cols);
|
|
13
|
+
if (columns < TODO_RAIL_MIN_TOTAL_COLS)
|
|
65
14
|
return 0;
|
|
66
|
-
|
|
67
|
-
return Math.max(0, viewportBudget - reserved);
|
|
15
|
+
return Math.min(TODO_RAIL_MAX_COLS, Math.max(TODO_RAIL_MIN_COLS, Math.round(columns * RAIL_FRACTION)));
|
|
68
16
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { SubagentRun, TeamSnapshot } from '../../team/types.js';
|
|
2
|
+
export interface TeamPanelLayout {
|
|
3
|
+
collapsed: boolean;
|
|
4
|
+
rowCount: number;
|
|
5
|
+
visible: SubagentRun[];
|
|
6
|
+
hiddenTotal: number;
|
|
7
|
+
hiddenRunning: number;
|
|
8
|
+
}
|
|
9
|
+
export interface TeamPanelLayoutInput {
|
|
10
|
+
snapshot: TeamSnapshot | null;
|
|
11
|
+
terminalRows: number;
|
|
12
|
+
availableRows: number;
|
|
13
|
+
}
|
|
14
|
+
/** Project actual roster rows, preserving three middle rows when expanded.
|
|
15
|
+
* Invalid geometry becomes zero; legacy callers may pass positive Infinity
|
|
16
|
+
* for an unlimited available-row budget. Does not throw or mutate the snapshot.
|
|
17
|
+
*/
|
|
18
|
+
export declare function buildTeamPanelLayout(input: TeamPanelLayoutInput): TeamPanelLayout;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { TEAM_LIMITS } from '../../team/limits.js';
|
|
2
|
+
import { selectPanelRows } from '../../team/panel-rows.js';
|
|
3
|
+
import { TODO_LIMITS } from '../../todo/limits.js';
|
|
4
|
+
/** Project actual roster rows, preserving three middle rows when expanded.
|
|
5
|
+
* Invalid geometry becomes zero; legacy callers may pass positive Infinity
|
|
6
|
+
* for an unlimited available-row budget. Does not throw or mutate the snapshot.
|
|
7
|
+
*/
|
|
8
|
+
export function buildTeamPanelLayout(input) {
|
|
9
|
+
const available = input.availableRows === Infinity ? Infinity : nonNegativeInt(input.availableRows);
|
|
10
|
+
if (!input.snapshot || available < 1) {
|
|
11
|
+
return { collapsed: true, rowCount: 0, visible: [], hiddenTotal: 0, hiddenRunning: 0 };
|
|
12
|
+
}
|
|
13
|
+
const selection = selectPanelRows(input.snapshot.runs, TEAM_LIMITS.panelMaxRows);
|
|
14
|
+
const natural = 1 + selection.visible.length + Number(selection.hiddenTotal > 0)
|
|
15
|
+
+ Number(Boolean(input.snapshot.lastMessage));
|
|
16
|
+
const collapsed = nonNegativeInt(input.terminalRows) < TEAM_LIMITS.panelCollapseRows
|
|
17
|
+
|| available - natural < TODO_LIMITS.panelMinRows;
|
|
18
|
+
return { ...selection, collapsed, rowCount: collapsed ? 1 : natural };
|
|
19
|
+
}
|
|
20
|
+
function nonNegativeInt(value) {
|
|
21
|
+
return Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
|
|
22
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { RenderMode } from './frame.js';
|
|
2
|
+
export interface TodoRailLayoutInput {
|
|
3
|
+
mode: RenderMode;
|
|
4
|
+
cols: number;
|
|
5
|
+
panelEnabled: boolean;
|
|
6
|
+
overlayOpen: boolean;
|
|
7
|
+
itemCount: number;
|
|
8
|
+
viewportBudget: number;
|
|
9
|
+
teamRows: number;
|
|
10
|
+
popupRows: number;
|
|
11
|
+
}
|
|
12
|
+
export interface TodoRailLayout {
|
|
13
|
+
visible: boolean;
|
|
14
|
+
reason: 'visible' | 'inline' | 'disabled' | 'overlay' | 'empty' | 'narrow' | 'short';
|
|
15
|
+
width: number;
|
|
16
|
+
rows: number;
|
|
17
|
+
contentCols: number;
|
|
18
|
+
popupMaxHeight: number;
|
|
19
|
+
}
|
|
20
|
+
/** Allocate columns and rows from the same team/menu projections as the renderers.
|
|
21
|
+
* Invalid geometry becomes zero, excess team/menu rows are clamped. Menu capacity
|
|
22
|
+
* never depends on menu occupancy or rail visibility, preventing feedback loops.
|
|
23
|
+
*/
|
|
24
|
+
export declare function buildTodoRailLayout(input: TodoRailLayoutInput): TodoRailLayout;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { TODO_LIMITS } from '../../todo/limits.js';
|
|
2
|
+
import { todoRailWidth } from './rail.js';
|
|
3
|
+
/** Allocate columns and rows from the same team/menu projections as the renderers.
|
|
4
|
+
* Invalid geometry becomes zero, excess team/menu rows are clamped. Menu capacity
|
|
5
|
+
* never depends on menu occupancy or rail visibility, preventing feedback loops.
|
|
6
|
+
*/
|
|
7
|
+
export function buildTodoRailLayout(input) {
|
|
8
|
+
const cols = nonNegativeInt(input.cols);
|
|
9
|
+
const budget = nonNegativeInt(input.viewportBudget);
|
|
10
|
+
const teamRows = Math.min(budget, nonNegativeInt(input.teamRows));
|
|
11
|
+
const popupMaxHeight = Math.max(0, budget - teamRows - TODO_LIMITS.panelMinRows);
|
|
12
|
+
const popupRows = popupMaxHeight < 3 ? 0 : Math.min(nonNegativeInt(input.popupRows), popupMaxHeight);
|
|
13
|
+
const rows = budget - teamRows - popupRows;
|
|
14
|
+
const width = todoRailWidth(cols);
|
|
15
|
+
const reason = input.mode !== 'fullscreen' ? 'inline'
|
|
16
|
+
: !input.panelEnabled ? 'disabled'
|
|
17
|
+
: input.overlayOpen ? 'overlay'
|
|
18
|
+
: nonNegativeInt(input.itemCount) === 0 ? 'empty'
|
|
19
|
+
: width === 0 ? 'narrow'
|
|
20
|
+
: rows < TODO_LIMITS.panelMinRows ? 'short'
|
|
21
|
+
: 'visible';
|
|
22
|
+
const visible = reason === 'visible';
|
|
23
|
+
return { visible, reason, width, rows, contentCols: cols - (visible ? width : 0), popupMaxHeight };
|
|
24
|
+
}
|
|
25
|
+
function nonNegativeInt(value) {
|
|
26
|
+
return Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
|
|
27
|
+
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* transcript in the terminal's native scrollback, and replaying there would
|
|
7
7
|
* simply print everything twice.
|
|
8
8
|
*/
|
|
9
|
-
import { formatCost, formatDuration, formatTokens } from '../agent/usage.js';
|
|
9
|
+
import { formatCost, formatDuration, formatTokens, promptTokensOf } from '../agent/usage.js';
|
|
10
10
|
import { formatRetryLine } from '../agent/retry-view.js';
|
|
11
11
|
const DEFAULT_MAX_ENTRIES = 200;
|
|
12
12
|
const CONTINUATION_INDENT = ' ';
|
|
@@ -199,7 +199,11 @@ function renderEntry(entry, glyphs) {
|
|
|
199
199
|
export function renderTranscriptText(entries, opts) {
|
|
200
200
|
const turns = entries.filter((e) => e.kind === 'user').length;
|
|
201
201
|
const g = opts.glyphs;
|
|
202
|
-
|
|
202
|
+
// THE SAME PROMPT-SIDE TOTAL THE STATUS BAR SHOWS (context-usage-gauge-accuracy
|
|
203
|
+
// §3.6). This line is what a user compares against the bar they were watching a
|
|
204
|
+
// second earlier, so a different unit here would look like the exit summary
|
|
205
|
+
// losing tokens.
|
|
206
|
+
const tokens = `${formatTokens(promptTokensOf(opts.usageTotal))}${g.arrowUp} ${formatTokens(opts.usageTotal.outputTokens)}${g.arrowDown}`;
|
|
203
207
|
const summary = [
|
|
204
208
|
'aragon',
|
|
205
209
|
`${opts.provider}:${opts.model}`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aragon-agent/cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.7",
|
|
4
4
|
"description": "AragonAgent CLI — a Claude-Code / Codex-style interactive terminal UI (TUI) on top of the @aragon-agent/core engine. Installable and runnable via npm / npx.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "AragonAgent contributors",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"prepublishOnly": "npm run build"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@aragon-agent/core": "^0.2.
|
|
59
|
+
"@aragon-agent/core": "^0.2.19",
|
|
60
60
|
"chalk": "^5.6.2",
|
|
61
61
|
"cli-highlight": "^2.0.0",
|
|
62
62
|
"commander": "^12.1.0",
|