@aragon-agent/cli 0.6.8 → 0.6.9

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.
Files changed (78) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +38 -37
  3. package/dist/agent/controller.d.ts +1 -1
  4. package/dist/agent/reducer.d.ts +7 -113
  5. package/dist/agent/reducer.js +0 -62
  6. package/dist/cli.js +48 -149
  7. package/dist/commands/perf.d.ts +32 -2
  8. package/dist/commands/perf.js +32 -2
  9. package/dist/commands/registry.d.ts +2 -2
  10. package/dist/config/env.js +0 -130
  11. package/dist/config/load.d.ts +0 -2
  12. package/dist/config/load.js +2 -31
  13. package/dist/config/schema.d.ts +1 -14
  14. package/dist/config/schema.js +0 -1
  15. package/dist/config/store.js +2 -0
  16. package/dist/logging/install.d.ts +2 -2
  17. package/dist/logging/install.js +2 -2
  18. package/dist/todo/follow-through.d.ts +0 -4
  19. package/dist/todo/follow-through.js +0 -4
  20. package/dist/todo/limits.d.ts +0 -10
  21. package/dist/todo/limits.js +0 -10
  22. package/dist/todo/prompt.d.ts +0 -15
  23. package/dist/ui/ActivityLine.d.ts +26 -34
  24. package/dist/ui/ActivityLine.js +30 -19
  25. package/dist/ui/App.d.ts +1 -41
  26. package/dist/ui/App.js +66 -128
  27. package/dist/ui/BottomStatusRow.d.ts +1 -3
  28. package/dist/ui/BottomStatusRow.js +4 -8
  29. package/dist/ui/Composer.d.ts +28 -1
  30. package/dist/ui/Composer.js +43 -17
  31. package/dist/ui/Header.d.ts +2 -16
  32. package/dist/ui/Header.js +3 -18
  33. package/dist/ui/Logo.d.ts +1 -17
  34. package/dist/ui/Logo.js +0 -16
  35. package/dist/ui/ModeChip.d.ts +7 -18
  36. package/dist/ui/ModeChip.js +9 -0
  37. package/dist/ui/PromptInput.d.ts +2 -28
  38. package/dist/ui/PromptInput.js +4 -7
  39. package/dist/ui/StatusBar.d.ts +0 -11
  40. package/dist/ui/ToastStack.d.ts +2 -18
  41. package/dist/ui/ToastStack.js +4 -11
  42. package/dist/ui/Transcript.d.ts +5 -71
  43. package/dist/ui/Transcript.js +9 -199
  44. package/dist/ui/entries/AssistantEntry.d.ts +0 -23
  45. package/dist/ui/entries/AssistantEntry.js +3 -4
  46. package/dist/ui/entries/DiffView.d.ts +1 -7
  47. package/dist/ui/entries/DiffView.js +2 -2
  48. package/dist/ui/entries/ToolCard.d.ts +0 -6
  49. package/dist/ui/entries/ToolCard.js +4 -13
  50. package/dist/ui/frame-differ.d.ts +13 -0
  51. package/dist/ui/frame-differ.js +36 -6
  52. package/dist/ui/ink-runtime.d.ts +1 -0
  53. package/dist/ui/ink-runtime.js +21 -0
  54. package/dist/ui/layout/AppShell.d.ts +1 -10
  55. package/dist/ui/layout/AppShell.js +2 -5
  56. package/dist/ui/layout/OverlayFrame.d.ts +1 -25
  57. package/dist/ui/layout/OverlayFrame.js +0 -24
  58. package/dist/ui/layout/ScrollViewport.d.ts +3 -0
  59. package/dist/ui/layout/ScrollViewport.js +17 -5
  60. package/dist/ui/layout/frame.d.ts +0 -22
  61. package/dist/ui/layout/frame.js +0 -36
  62. package/dist/ui/layout/todo-layout.d.ts +1 -3
  63. package/dist/ui/layout/todo-layout.js +7 -8
  64. package/dist/ui/layout/viewport-geometry.d.ts +1 -1
  65. package/dist/ui/layout/viewport-geometry.js +1 -1
  66. package/dist/ui/live-clamp.d.ts +1 -29
  67. package/dist/ui/live-clamp.js +1 -29
  68. package/dist/ui/overlays/HelpOverlay.js +1 -0
  69. package/dist/ui/run-status-row.d.ts +61 -0
  70. package/dist/ui/run-status-row.js +70 -0
  71. package/dist/ui/transcript-text.d.ts +0 -8
  72. package/dist/ui/transcript-text.js +0 -8
  73. package/dist/ui/use-startup-notices.d.ts +1 -57
  74. package/dist/ui/use-startup-notices.js +3 -54
  75. package/dist/ui/use-wheel-routing.d.ts +1 -1
  76. package/package.json +2 -2
  77. package/dist/ui/TodoStrip.d.ts +0 -36
  78. package/dist/ui/TodoStrip.js +0 -32
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  /**
3
3
  * Composer (spec §4.7) — the thin shell around the existing `PromptInput`.
4
4
  *
@@ -15,11 +15,30 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
15
15
  */
16
16
  import { useCallback, useState } from 'react';
17
17
  import { Box, Text } from 'ink';
18
+ import stringWidth from 'string-width';
18
19
  import { pickGlyphs } from './glyphs.js';
19
20
  import { HINT_FADE_AFTER } from '../config/schema.js';
20
21
  import { PromptInput } from './PromptInput.js';
21
- import { ModeChip } from './ModeChip.js';
22
+ import { ModeChip, modeChipCols } from './ModeChip.js';
23
+ import { ActivityLabel, resolveActivityLabel } from './ActivityLine.js';
24
+ import { RUN_ROW_LEAD, planRunRow } from './run-status-row.js';
22
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
+ */
33
+ export function runningHintClauses(opts) {
34
+ const { glyphs, services } = opts;
35
+ const clauses = [`${glyphs.enterKey} steer`, `esc${glyphs.times}2 interrupt`];
36
+ // Confirmation is named even when service controls also need space.
37
+ if (services > 0)
38
+ clauses.push(`ctrl+c stop ${services}`);
39
+ clauses.push(`ctrl+c${glyphs.times}2 exit`);
40
+ return clauses;
41
+ }
23
42
  /**
24
43
  * Build the hint row.
25
44
  *
@@ -40,18 +59,8 @@ function hintText(opts) {
40
59
  // clauses are shown; the row is `wrap="truncate"` and they are ordered
41
60
  // most-urgent-first, so a terminal too narrow for all of them drops the exit
42
61
  // clause last rather than the abort key.
43
- if (running) {
44
- if (services > 0) {
45
- return [
46
- `${glyphs.enterKey} steer`,
47
- // Confirmation is named even when service controls also need space.
48
- `esc${glyphs.times}2 interrupt`,
49
- `ctrl+c stop ${services}`,
50
- `ctrl+c${glyphs.times}2 exit`,
51
- ].join(dot);
52
- }
53
- return [`${glyphs.enterKey} steer`, `esc${glyphs.times}2 interrupt`, `ctrl+c${glyphs.times}2 exit`].join(dot);
54
- }
62
+ if (running)
63
+ return runningHintClauses({ glyphs, services }).join(dot);
55
64
  // The hint names the DESTINATION, not the current state — the correct label
56
65
  // for a toggle affordance, and the reason the mode word here is the OPPOSITE
57
66
  // of the one on the chip beside it.
@@ -86,7 +95,21 @@ function hintText(opts) {
86
95
  * than fishing the string out of a rendered frame.
87
96
  */
88
97
  export const hintTextForTest = hintText;
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, }) {
98
+ function RunRow({ runRow, cols, services, agentMode, theme, caps, }) {
99
+ const glyphs = pickGlyphs(caps);
100
+ const separator = ` ${glyphs.midDot} `;
101
+ const clauses = runningHintClauses({ glyphs, services });
102
+ const plan = planRunRow({
103
+ cols,
104
+ // Spinner glyph + the space after it, then the label.
105
+ labelCols: 2 + stringWidth(resolveActivityLabel(runRow.activity, glyphs)),
106
+ hintClauseCols: clauses.map((clause) => stringWidth(clause)),
107
+ separatorCols: stringWidth(separator),
108
+ chipCols: modeChipCols(agentMode),
109
+ });
110
+ 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 })] }))] }));
111
+ }
112
+ 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, }) {
90
113
  const [hasDraft, setHasDraft] = useState(false);
91
114
  const onDraftChange = useCallback((next) => {
92
115
  setHasDraft(next.hasDraft);
@@ -105,8 +128,11 @@ export function Composer({ cols, cursorVisible, onInteraction, measureRef, isAct
105
128
  ? theme.focusBorder ?? theme.primary
106
129
  : theme.idleBorder ?? theme.border;
107
130
  const glyphs = pickGlyphs(caps);
108
- const visible = showHint && hintsEnabled;
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 && (
131
+ // While the run row is up it REPLACES the hint row below the input (same row
132
+ // count, so starting a run never moves the layout).
133
+ const runRowActive = running && !!runRow;
134
+ const visible = showHint && hintsEnabled && !runRowActive;
135
+ 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 && (
110
136
  // The chip rides the EXISTING hint row rather than adding one, so it
111
137
  // costs zero rows on a 24-row terminal.
112
138
  _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({
@@ -1,23 +1,9 @@
1
- /**
2
- * Header (spec §4.2) — the always-present brand bar.
3
- *
4
- * In full-screen this is EXACTLY ONE ROW at every size, which is what makes the
5
- * viewport height monotonic in `rows` and the first submit jump-free. The 6-row
6
- * wordmark it used to host now lives in `SessionOpener`, inside the viewport.
7
- *
8
- * `banner` survives for the inline path only (`App.tsx` picks it with a local
9
- * ternary and never calls `pickHeaderVariant`), so inline keeps its v0.3.0
10
- * geometry. `art` is no longer reachable from here.
11
- *
12
- * Every line is `wrap="truncate"`: a long cwd silently costing a second row
13
- * would push the bottom chrome off-frame and break R2.
14
- */
1
+ /** One-row brand bar, with a compact variant for narrow terminals. */
15
2
  import React from 'react';
16
3
  import type { Theme } from './theme.js';
17
4
  import type { TermCapabilities } from './capabilities.js';
18
5
  import { type HeaderVariant } from './Logo.js';
19
6
  interface HeaderProps {
20
- version: string;
21
7
  cwd: string;
22
8
  provider: string;
23
9
  model: string;
@@ -26,5 +12,5 @@ interface HeaderProps {
26
12
  theme: Theme;
27
13
  caps: TermCapabilities;
28
14
  }
29
- export declare function Header({ version, cwd, provider, model, hasKey, variant, theme, caps, }: HeaderProps): React.ReactElement;
15
+ export declare function Header({ cwd, provider, model, hasKey, variant, theme, caps, }: HeaderProps): React.ReactElement;
30
16
  export {};
package/dist/ui/Header.js CHANGED
@@ -1,25 +1,10 @@
1
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
- import { homedir } from 'node:os';
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
2
  import { basename } from 'node:path';
4
3
  import { Box, Text } from 'ink';
5
4
  import { Logo } from './Logo.js';
6
- /** Collapse the home directory to `~` for a shorter path display. */
7
- function tildeCwd(cwd) {
8
- const home = homedir();
9
- if (home && cwd.startsWith(home))
10
- return `~${cwd.slice(home.length)}`;
11
- return cwd;
12
- }
13
- function KeyDot({ hasKey, theme }) {
14
- return (_jsxs(Text, { color: hasKey ? theme.toolDone : theme.toolRunning, children: [hasKey ? theme.symbols.keyOn : theme.symbols.keyOff, " ", hasKey ? 'key set' : 'no key'] }));
15
- }
16
- export function Header({ version, cwd, provider, model, hasKey, variant, theme, caps, }) {
5
+ export function Header({ cwd, provider, model, hasKey, variant, theme, caps, }) {
17
6
  if (variant === 'mini') {
18
7
  return (_jsx(Box, { flexDirection: "column", flexShrink: 0, children: _jsx(Text, { wrap: "truncate", children: _jsx(Logo, { variant: "mini", theme: theme, caps: caps }) }) }));
19
8
  }
20
- if (variant === 'bar') {
21
- return (_jsx(Box, { flexDirection: "column", flexShrink: 0, children: _jsxs(Text, { wrap: "truncate", children: [_jsx(Logo, { variant: "bar", theme: theme, caps: caps }), _jsxs(Text, { color: theme.accent, children: [' ', provider, ":", model] }), _jsxs(Text, { color: theme.muted, children: [' ', basename(cwd) || cwd] }), _jsxs(Text, { color: hasKey ? theme.toolDone : theme.toolRunning, children: [' ', hasKey ? theme.symbols.keyOn : theme.symbols.keyOff] })] }) }));
22
- }
23
- // `banner` (inline only): wordmark row + meta row.
24
- return (_jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [_jsx(Logo, { variant: variant, theme: theme, caps: caps }), _jsxs(Text, { wrap: "truncate", children: [_jsxs(Text, { color: theme.muted, children: ["v", version] }), _jsxs(Text, { color: theme.accent, children: [' ', provider, ":", model] }), _jsx(Text, { children: ' ' }), _jsx(KeyDot, { hasKey: hasKey, theme: theme }), _jsxs(Text, { color: theme.muted, children: [' ', tildeCwd(cwd)] })] })] }));
9
+ return (_jsx(Box, { flexDirection: "column", flexShrink: 0, children: _jsxs(Text, { wrap: "truncate", children: [_jsx(Logo, { variant: "bar", theme: theme, caps: caps }), _jsxs(Text, { color: theme.accent, children: [' ', provider, ":", model] }), _jsxs(Text, { color: theme.muted, children: [' ', basename(cwd) || cwd] }), _jsxs(Text, { color: hasKey ? theme.toolDone : theme.toolRunning, children: [' ', hasKey ? theme.symbols.keyOn : theme.symbols.keyOff] })] }) }));
25
10
  }
package/dist/ui/Logo.d.ts CHANGED
@@ -8,25 +8,9 @@ import type { TermCapabilities } from './capabilities.js';
8
8
  /** 6 rows × 52 columns, every row equal width (pinned by logo.test.ts). */
9
9
  export declare const LOGO_ART: readonly string[];
10
10
  export declare const LOGO_ART_WIDTH = 52;
11
- export type HeaderVariant = 'art' | 'banner' | 'bar' | 'mini';
11
+ export type HeaderVariant = 'bar' | 'mini';
12
12
  /** How the one-off opening wordmark renders inside the viewport (§4.2). */
13
13
  export type OpenerVariant = 'art' | 'banner' | 'none';
14
- /**
15
- * Pick the header tier (spec §4.2).
16
- *
17
- * `cols` is the only input, and that is the point. The old signature also took
18
- * `empty` and `rows` and could return `art`, so the brand region was 8 rows tall
19
- * on an empty session and 1 row afterwards: sending the first message made the
20
- * viewport jump by 7 rows, and widening a 27-row terminal to 28 rows made the
21
- * visible content SHRINK. A constant-height brand bar removes both, which is
22
- * what lets `budget.ts::viewportRows` be monotonic (A-3/A-4/A-9).
23
- *
24
- * The wordmark did not disappear — it moved into the viewport as
25
- * `SessionOpener`, where it scrolls away like any other content.
26
- *
27
- * NOTE: `Header` still renders `banner`; the inline path picks it directly at
28
- * `App.tsx` and does not call this function.
29
- */
30
14
  export declare function pickHeaderVariant(cols: number): HeaderVariant;
31
15
  /**
32
16
  * Pick the opening wordmark tier.
package/dist/ui/Logo.js CHANGED
@@ -22,22 +22,6 @@ const ART_MIN_VIEWPORT_ROWS = 14;
22
22
  const ART_MIN_COLS = LOGO_ART_WIDTH + 4;
23
23
  const BANNER_MIN_COLS = 48;
24
24
  const BAR_MIN_COLS = 40;
25
- /**
26
- * Pick the header tier (spec §4.2).
27
- *
28
- * `cols` is the only input, and that is the point. The old signature also took
29
- * `empty` and `rows` and could return `art`, so the brand region was 8 rows tall
30
- * on an empty session and 1 row afterwards: sending the first message made the
31
- * viewport jump by 7 rows, and widening a 27-row terminal to 28 rows made the
32
- * visible content SHRINK. A constant-height brand bar removes both, which is
33
- * what lets `budget.ts::viewportRows` be monotonic (A-3/A-4/A-9).
34
- *
35
- * The wordmark did not disappear — it moved into the viewport as
36
- * `SessionOpener`, where it scrolls away like any other content.
37
- *
38
- * NOTE: `Header` still renders `banner`; the inline path picks it directly at
39
- * `App.tsx` and does not call this function.
40
- */
41
25
  export function pickHeaderVariant(cols) {
42
26
  return cols >= BAR_MIN_COLS ? 'bar' : 'mini';
43
27
  }
@@ -1,21 +1,3 @@
1
- /**
2
- * ModeChip — the `PLAN` badge on the composer's hint row (plan-mode §6.1).
3
- *
4
- * `theme.chip = { fg, bg }` exists in every palette and was, until now, unused.
5
- * A mode badge is what it was for.
6
- *
7
- * SHOWN ONLY IN PLAN MODE. `BUILD` is the default, and a badge that is always on
8
- * is furniture rather than signal; it also means the Build-mode composer renders
9
- * exactly as it did before this feature existed.
10
- *
11
- * THE CHIP IS AN ENHANCEMENT, NOT THE GUARANTEE (P1-6). It rides the hint row,
12
- * which disappears on a short terminal (`showHint`), under `hints: false`, and
13
- * in inline render mode (which mounts a bare `PromptInput` and never mounts
14
- * `Composer` at all). The GUARANTEED carrier is the status-bar word, which
15
- * renders in both `AppShell` branches and has no opt-out. If you ever move the
16
- * mode indicator, that is the constraint to preserve: the status bar must always
17
- * name a non-default mode; the chip may.
18
- */
19
1
  import React from 'react';
20
2
  import type { Theme } from './theme.js';
21
3
  import type { TermCapabilities } from './capabilities.js';
@@ -25,5 +7,12 @@ interface ModeChipProps {
25
7
  theme: Theme;
26
8
  caps: TermCapabilities;
27
9
  }
10
+ /**
11
+ * Columns `ModeChip` occupies, including its trailing gap (plan => label + 3, any
12
+ * other mode 0). Both render branches (padded background chip, bracketed text)
13
+ * come to the same width, which is what lets the run status row budget for it
14
+ * without measuring a rendered element.
15
+ */
16
+ export declare function modeChipCols(mode: AgentMode): number;
28
17
  export declare function ModeChip({ mode, theme, caps }: ModeChipProps): React.ReactElement | null;
29
18
  export {};
@@ -1,6 +1,15 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Box, Text } from 'ink';
3
3
  import { MODE_LABEL } from '../agent/agent-mode.js';
4
+ /**
5
+ * Columns `ModeChip` occupies, including its trailing gap (plan => label + 3, any
6
+ * other mode 0). Both render branches (padded background chip, bracketed text)
7
+ * come to the same width, which is what lets the run status row budget for it
8
+ * without measuring a rendered element.
9
+ */
10
+ export function modeChipCols(mode) {
11
+ return mode === 'plan' ? MODE_LABEL[mode].length + 3 : 0;
12
+ }
4
13
  export function ModeChip({ mode, theme, caps }) {
5
14
  if (mode !== 'plan')
6
15
  return null;
@@ -62,15 +62,7 @@ interface PromptInputProps {
62
62
  * (see the first statement of the input handler).
63
63
  */
64
64
  agentMode?: AgentMode;
65
- /**
66
- * Set by `Composer` in full-screen mode: draw the rounded frame around the
67
- * input row only, so the autocomplete popup lands OUTSIDE it (spec §4.7)
68
- * without lifting the editor's buffer/cursor state out of this component.
69
- * Unset ⇒ the v0.2.0 borderless inline rendering.
70
- */
71
- bordered?: {
72
- color?: string;
73
- };
65
+ borderColor?: string;
74
66
  /**
75
67
  * Fires when the draft flips empty ⇄ non-empty (for the border color) OR when
76
68
  * its ROW COUNT changes (for `layout/budget.ts`) — never per keystroke.
@@ -95,24 +87,6 @@ interface PromptInputProps {
95
87
  * component is the only place that holds both facts.
96
88
  */
97
89
  onNotice?: (level: 'warn' | 'error', text: string) => void;
98
- /**
99
- * Rows of newer output hidden BELOW the viewport — `ScrollViewport`'s offset
100
- * (tui-selection-and-scroll-follow §4.2 / G7).
101
- *
102
- * Rendered as a right-aligned chip ON THE INPUT ROW, INSIDE THE BORDER, which
103
- * is what the requirement asks for (输入框内部) and what makes the hint cost
104
- * zero rows. It used to take the viewport's own last row, which spent a line of
105
- * the user's content exactly when the transcript was longest AND fed the number
106
- * back into its own derivation (§3.3).
107
- *
108
- * IT IS STATE, NOT A HINT. It deliberately ignores `showHint` / `hintsEnabled`
109
- * — those govern the teaching row BELOW the box, which fades with experience
110
- * and is dropped on a short terminal. "You are 12 rows behind the newest
111
- * output" must survive both (T-23).
112
- *
113
- * Only meaningful in full-screen mode, so it renders only when `bordered` is
114
- * set: inline mode has no self-drawn viewport and this is always 0 there.
115
- */
116
90
  scrolledLines?: number;
117
91
  }
118
92
  export interface ScrollChip {
@@ -179,5 +153,5 @@ export declare function fileTokenAt(buffer: string, cursor: number): {
179
153
  end: number;
180
154
  query: string;
181
155
  } | null;
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;
156
+ 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;
183
157
  export {};
@@ -248,7 +248,7 @@ function isControlSeq(input) {
248
248
  // ---------------------------------------------------------------------------
249
249
  // Component
250
250
  // ---------------------------------------------------------------------------
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, }) {
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, borderColor, onDraftChange, onNotice, scrolledLines = 0, }) {
252
252
  const { stdout } = useStdout();
253
253
  const [editor, dispatch] = useReducer(editorReducer, INITIAL_EDITOR_STATE);
254
254
  const { buffer, cursor, historyIndex, dismissed, sel } = editor;
@@ -317,14 +317,11 @@ export function PromptInput({ cols: colsProp, cursorVisible = true, onInteractio
317
317
  const glyphs = pickGlyphs(caps);
318
318
  const cols = colsProp ?? stdout?.columns ?? 80;
319
319
  const terminalRows = stdout?.rows ?? 24;
320
- // Full-screen only: inline mode has no self-drawn viewport, so `scrolledLines`
321
- // is always 0 there and the branch is unreachable rather than merely unused.
322
- // The guard is explicit so it stays that way.
323
- const chip = bordered ? scrollChip(scrolledLines, cols, glyphs) : null;
320
+ const chip = scrollChip(scrolledLines, cols, glyphs);
324
321
  // Err NARROW. Wrapping wider than the box Ink gives us would make Ink wrap a
325
322
  // second time, and then `layout.rows.length` is a lie — which is the one thing
326
323
  // this module may not be.
327
- const chromeCols = bordered ? 2 /* border */ + 2 /* paddingX */ + 2 /* marker */ : 2;
324
+ const chromeCols = 2 /* border */ + 2 /* paddingX */ + 2 /* marker */;
328
325
  const baseCols = Math.max(8, cols - chromeCols - (chip ? chip.cells + 1 : 0));
329
326
  const measure = (wrapCols) => layoutComposer({
330
327
  buffer,
@@ -599,5 +596,5 @@ export function PromptInput({ cols: colsProp, cursorVisible = true, onInteractio
599
596
  // the user is typing — `↓ 9` becomes `↓ 10` becomes `↓ 100` — and every
600
597
  // change would otherwise re-wrap a draft that fills the line.
601
598
  _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 }) }))] }));
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)] }));
599
+ return (_jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [popupVisible && (_jsx(AutocompletePopup, { items: popupItems, selected: clampedSel, theme: theme, caps: caps, maxRows: popupMaxRows, layout: popupLayout })), (_jsx(Box, { flexDirection: "column", flexShrink: 0, borderStyle: glyphs.boxStyle, borderColor: borderColor, paddingX: 1, children: inputRow }))] }));
603
600
  }
@@ -52,17 +52,6 @@ interface StatusBarProps {
52
52
  * Ctrl+L redraw carrier — invariant I-5 (spec §4.13). See `redrawChar`.
53
53
  */
54
54
  redrawNonce?: number;
55
- /**
56
- * The session mode. THIS IS THE GUARANTEED MODE INDICATOR (plan-mode §6.4 /
57
- * P1-6): the composer chip disappears on a short terminal, under
58
- * `hints: false`, and in inline render mode, but `StatusBar` renders in both
59
- * `AppShell` branches and its left cluster has no opt-out and no width gate.
60
- *
61
- * Design intent, stated here so it survives a later refactor: the status bar
62
- * must ALWAYS name a non-default mode; the chip may. Do not give this a
63
- * `cols >= N` breakpoint, and do not move it into the right cluster — those
64
- * children are flexible and drop characters under pressure.
65
- */
66
55
  agentMode?: AgentMode;
67
56
  /** Non-null while a deferred `plan -> build` waits for `agent_end`. */
68
57
  pendingAgentMode?: AgentMode | null;
@@ -1,24 +1,8 @@
1
- /**
2
- * Toast strip (spec §4.8). Ephemeral acks created by App/commands and
3
- * auto-dismissed by App's TTL effect; the reducer stays timer-free.
4
- *
5
- * In `fullscreen` this ALWAYS occupies exactly one row — an empty row when there
6
- * is nothing to say. Rendering conditionally would make the viewport height (and
7
- * therefore the whole transcript) jump every time a toast appears or expires.
8
- * There is deliberately no `rows` threshold: jitter is a layout property, not a
9
- * space property, and a short terminal suffers it proportionally MORE, so the
10
- * tier that most needs the fixed row is exactly the one a threshold would skip.
11
- * Only the newest toast is shown; older ones collapse into a `+N` counter.
12
- *
13
- * `inline` keeps the v0.2.0 conditional 0–3 row stack: without a fixed frame,
14
- * growing and shrinking is just normal document flow.
15
- */
1
+ /** Fixed one-row toast strip; only the newest toast is shown. */
16
2
  import React from 'react';
17
3
  import type { Theme } from './theme.js';
18
4
  import type { Toast } from '../agent/reducer.js';
19
- import type { RenderMode } from './layout/frame.js';
20
- export declare function ToastStack({ toasts, theme, mode, }: {
5
+ export declare function ToastStack({ toasts, theme, }: {
21
6
  toasts: Toast[];
22
7
  theme: Theme;
23
- mode?: RenderMode;
24
8
  }): React.ReactElement | null;
@@ -1,6 +1,5 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Box, Text } from 'ink';
3
- const MAX_VISIBLE = 3;
4
3
  function toastColor(level, theme) {
5
4
  switch (level) {
6
5
  case 'success':
@@ -25,14 +24,8 @@ function toastGlyph(level, theme) {
25
24
  return theme.symbols.info;
26
25
  }
27
26
  }
28
- export function ToastStack({ toasts, theme, mode = 'inline', }) {
29
- if (mode === 'fullscreen') {
30
- const latest = toasts[toasts.length - 1];
31
- const extra = Math.max(0, toasts.length - 1);
32
- return (_jsx(Box, { flexShrink: 0, children: latest ? (_jsxs(Text, { wrap: "truncate", color: toastColor(latest.level, theme), children: [' ', toastGlyph(latest.level, theme), " ", latest.text, extra > 0 ? ` +${extra}` : ''] })) : (_jsx(Text, { children: " " })) }));
33
- }
34
- const shown = toasts.slice(-MAX_VISIBLE);
35
- if (shown.length === 0)
36
- return null;
37
- return (_jsx(Box, { flexDirection: "column", marginTop: 1, children: shown.map((t) => (_jsxs(Text, { color: toastColor(t.level, theme), children: [toastGlyph(t.level, theme), " ", t.text] }, t.id))) }));
27
+ export function ToastStack({ toasts, theme, }) {
28
+ const latest = toasts[toasts.length - 1];
29
+ const extra = Math.max(0, toasts.length - 1);
30
+ return (_jsx(Box, { flexShrink: 0, children: latest ? (_jsxs(Text, { wrap: "truncate", color: toastColor(latest.level, theme), children: [' ', toastGlyph(latest.level, theme), " ", latest.text, extra > 0 ? ` +${extra}` : ''] })) : (_jsx(Text, { children: " " })) }));
38
31
  }
@@ -1,65 +1,23 @@
1
- /**
2
- * Transcript (spec §3.8). Splits the entry list into a settled prefix rendered
3
- * once into Ink's `<Static>` (native terminal scrollback, never re-rendered)
4
- * and a live tail rendered normally. This kills per-token full re-renders and
5
- * flicker on long/fast sessions.
6
- *
7
- * `computeSettledCount` is pure and exported for `transcript-static.test.ts`.
8
- * The settled boundary is held MONOTONIC at render time: a previously-settled
9
- * entry never leaves `<Static>` (which cannot un-print it) even if it later
10
- * becomes expanded, so the live tail can never duplicate a scrolled-off entry.
11
- *
12
- * `EntryView` is shared by both render paths ON PURPOSE (§4.3 / §12). Forking it
13
- * to freeze inline's v0.2.0 output would mean writing every future entry-layer
14
- * change twice and watching the two drift; inline is a degradation path, not a
15
- * parallel product. What inline still guarantees is its GEOMETRY contract — no
16
- * fixed frame, `<Static>` keeps the settled history, no private ANSI — not its
17
- * pixel-for-pixel appearance.
18
- *
19
- * Two things changed with tui-render-performance:
20
- * - `EntryView` is `React.memo`'d with an explicit comparator (L2 / R3), which
21
- * is only worth anything because `mapEntry` already preserves object
22
- * identity for untouched entries;
23
- * - `TranscriptList` (the FULL-SCREEN body) mounts only the entries inside the
24
- * viewport and replaces the rest with two spacer boxes (L3 / R1), and the
25
- * inline `Transcript` clamps its live region so `ink.js:121` is unreachable
26
- * (L5 / R6).
27
- */
1
+ /** Virtualized full-screen transcript and entry cards. */
2
+ /** Virtualized full-screen transcript and shared entry cards. */
28
3
  import React from 'react';
29
4
  import { type Theme } from './theme.js';
30
5
  import type { TermCapabilities } from './capabilities.js';
31
6
  import { type DensityMode } from './density.js';
32
7
  import type { Entry } from '../agent/reducer.js';
33
- import type { RenderMode } from './layout/frame.js';
34
8
  import { type TailSink } from './layout/follow-state.js';
35
9
  import type { HeightStore } from './use-height-store.js';
36
- /**
37
- * Largest prefix length P such that `entries[0..P)` are all terminal, none is
38
- * within the last `LIVE_TAIL` entries, and none is currently expanded. Pure and
39
- * non-decreasing as entries terminalize (expanding a card can lower it — the
40
- * component clamps with a monotonic high-water mark to avoid Static duplication).
41
- */
42
- export declare function computeSettledCount(entries: Entry[], expandedToolIds: Record<string, true>): number;
43
10
  interface TranscriptProps {
44
11
  entries: Entry[];
45
12
  expandedToolIds: Record<string, true>;
46
13
  thinkingVisible: boolean;
47
14
  reducedMotion: boolean;
48
15
  density: DensityMode;
49
- /**
50
- * The render mode, threaded from `App` as a plain scalar prop.
51
- *
52
- * It decides whether the collapsed thinking marker may offer `ctrl+t` (D-16):
53
- * inline prints settled entries into `<Static>`, which cannot re-print them.
54
- * NOT read from a module-level singleton — `render-memo.test.tsx` asserts prop
55
- * stability across frames, and a hidden read would be invisible to it.
56
- */
57
- mode: RenderMode;
58
16
  /**
59
17
  * Wall-clock seconds, threaded from `App`'s existing 200 ms ticker, for the
60
18
  * live tool card's stall row (agent-activity-presentation-live §3.3.4 / D-36).
61
19
  *
62
- * BOTH MAP CALLBACKS BELOW DECIDE PER ENTRY WHETHER TO PASS IT ON, and neither
20
+ * The entry map decides per entry whether to pass it on, and it
63
21
  * may pass it unconditionally: `ToolCard` is `React.memo` with the default
64
22
  * comparator, so a prop that changes every second on every card would defeat
65
23
  * that boundary for the whole transcript.
@@ -76,15 +34,12 @@ export interface EntryViewProps {
76
34
  thinkingVisible: boolean;
77
35
  reducedMotion: boolean;
78
36
  density: DensityMode;
79
- mode: RenderMode;
80
37
  theme: Theme;
81
38
  caps: TermCapabilities;
82
- /** Inline mode only (L5); `undefined` in full-screen, which means no clamp. */
83
- liveClampRows?: number;
84
39
  /** Wall-clock seconds; set ONLY on a running tool entry (§3.3.4 / P1-3). */
85
40
  nowSec?: number;
86
41
  }
87
- declare function EntryViewImpl({ entry, prev, expanded, thinkingVisible, reducedMotion, density, mode, theme, caps, liveClampRows, nowSec, }: EntryViewProps): React.ReactElement | null;
42
+ declare function EntryViewImpl({ entry, prev, expanded, thinkingVisible, reducedMotion, density, theme, caps, nowSec, }: EntryViewProps): React.ReactElement | null;
88
43
  /**
89
44
  * THE memo boundary that makes a long transcript cheap (L2 / R3).
90
45
  *
@@ -100,27 +55,7 @@ declare function EntryViewImpl({ entry, prev, expanded, thinkingVisible, reduced
100
55
  * test rather than a silent regression.
101
56
  */
102
57
  export declare const EntryView: React.MemoExoticComponent<typeof EntryViewImpl>;
103
- /**
104
- * Full-screen transcript body — the same entry renderers with NO `<Static>`.
105
- *
106
- * `<Static>` prints above the live frame; once the frame is `rows - 1` tall
107
- * there is exactly one visible line up there, so the mechanism stops being a
108
- * history view and starts being a leak. Full-screen therefore owns its history
109
- * inside `ScrollViewport` instead.
110
- *
111
- * `computeSettledCount` is deliberately NOT called here: its only job is to keep
112
- * an entry that already reached `<Static>` from flowing back into the live
113
- * region, and with no Static there is nothing to protect. The settled high-water
114
- * mark feeds nothing else — `/save` serializes `entries` in full — so leaving it
115
- * un-advanced has no side effect.
116
- *
117
- * VIRTUALISED (L3 / R1). `windowSize` is now the SCROLL HORIZON — how far back
118
- * the user can scroll — and no longer the rendering budget, because entries
119
- * outside the viewport are not mounted at all. What bounds the frame is
120
- * `selectWindow`, and the two spacers are what keep the scroll maths honest
121
- * while they are absent.
122
- */
123
- export declare function TranscriptList({ entries, expandedToolIds, thinkingVisible, reducedMotion, density, mode, nowSec, theme, caps, windowSize, cols, heights, mountedSink, tailSink, }: TranscriptProps & {
58
+ export declare function TranscriptList({ entries, expandedToolIds, thinkingVisible, reducedMotion, density, nowSec, theme, caps, windowSize, cols, heights, mountedSink, tailSink, }: TranscriptProps & {
124
59
  windowSize: number;
125
60
  /** The viewport's own width — narrower than the terminal when the rail is up. */
126
61
  cols: number;
@@ -153,5 +88,4 @@ export declare function TranscriptList({ entries, expandedToolIds, thinkingVisib
153
88
  */
154
89
  tailSink?: TailSink;
155
90
  }): React.ReactElement;
156
- export declare function Transcript({ entries, expandedToolIds, thinkingVisible, reducedMotion, density, mode, nowSec, theme, caps, }: TranscriptProps): React.ReactElement;
157
91
  export {};