@aragon-agent/cli 0.6.11 → 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.
Files changed (115) hide show
  1. package/CHANGELOG.md +10 -1
  2. package/README.md +97 -20
  3. package/dist/agent/controller.d.ts +38 -3
  4. package/dist/agent/controller.js +151 -60
  5. package/dist/agent/model-profile-settings.d.ts +31 -0
  6. package/dist/agent/model-profile-settings.js +199 -0
  7. package/dist/agent/queued-messages.d.ts +20 -0
  8. package/dist/agent/queued-messages.js +46 -0
  9. package/dist/agent/reducer.d.ts +21 -2
  10. package/dist/agent/reducer.js +65 -37
  11. package/dist/cli.js +15 -1
  12. package/dist/commands/builtins.js +83 -42
  13. package/dist/commands/queue-command.d.ts +3 -0
  14. package/dist/commands/queue-command.js +5 -0
  15. package/dist/commands/registry.d.ts +3 -0
  16. package/dist/compaction/child.d.ts +4 -2
  17. package/dist/compaction/child.js +4 -1
  18. package/dist/compaction/compactor.d.ts +7 -2
  19. package/dist/compaction/compactor.js +13 -11
  20. package/dist/compaction/context-command.js +5 -0
  21. package/dist/compaction/wiring.d.ts +4 -3
  22. package/dist/compaction/wiring.js +6 -1
  23. package/dist/config/cli-commands.d.ts +2 -0
  24. package/dist/config/cli-commands.js +16 -4
  25. package/dist/config/load.d.ts +2 -1
  26. package/dist/config/load.js +15 -8
  27. package/dist/config/model-profile-resolution.d.ts +20 -0
  28. package/dist/config/model-profile-resolution.js +81 -0
  29. package/dist/config/model-profile-store.d.ts +67 -0
  30. package/dist/config/model-profile-store.js +153 -0
  31. package/dist/config/model-profiles.d.ts +44 -0
  32. package/dist/config/model-profiles.js +121 -0
  33. package/dist/config/schema.d.ts +9 -0
  34. package/dist/config/store.d.ts +11 -1
  35. package/dist/config/store.js +43 -27
  36. package/dist/fast/resolve.d.ts +2 -1
  37. package/dist/fast/resolve.js +6 -4
  38. package/dist/fast/wiring.d.ts +5 -3
  39. package/dist/fast/wiring.js +8 -4
  40. package/dist/input/enter-sequences.d.ts +11 -10
  41. package/dist/input/enter-sequences.js +29 -27
  42. package/dist/input/stdin-filter.js +93 -28
  43. package/dist/logging/install.d.ts +3 -0
  44. package/dist/logging/install.js +4 -2
  45. package/dist/logging/secret-registry.d.ts +2 -0
  46. package/dist/logging/secret-registry.js +18 -0
  47. package/dist/session/persist.d.ts +6 -7
  48. package/dist/session/persist.js +6 -9
  49. package/dist/session/store.d.ts +4 -1
  50. package/dist/session/validate-session.d.ts +3 -0
  51. package/dist/session/validate-session.js +207 -0
  52. package/dist/team/runtime.d.ts +2 -1
  53. package/dist/team/subagent.d.ts +3 -1
  54. package/dist/team/subagent.js +3 -1
  55. package/dist/ui/ActivityLine.d.ts +6 -70
  56. package/dist/ui/ActivityLine.js +12 -34
  57. package/dist/ui/App.d.ts +1 -0
  58. package/dist/ui/App.js +161 -226
  59. package/dist/ui/BottomStatusRow.d.ts +6 -86
  60. package/dist/ui/BottomStatusRow.js +11 -26
  61. package/dist/ui/Composer.d.ts +18 -84
  62. package/dist/ui/Composer.js +9 -100
  63. package/dist/ui/PromptInput.d.ts +6 -2
  64. package/dist/ui/PromptInput.js +91 -105
  65. package/dist/ui/QueuePanel.d.ts +8 -0
  66. package/dist/ui/QueuePanel.js +9 -0
  67. package/dist/ui/QueueStatusRow.d.ts +24 -0
  68. package/dist/ui/QueueStatusRow.js +51 -0
  69. package/dist/ui/StatusBar.d.ts +15 -149
  70. package/dist/ui/StatusBar.js +27 -112
  71. package/dist/ui/clipboard-task.d.ts +29 -0
  72. package/dist/ui/clipboard-task.js +66 -0
  73. package/dist/ui/clipboard.d.ts +29 -50
  74. package/dist/ui/clipboard.js +141 -75
  75. package/dist/ui/composer-input.d.ts +28 -0
  76. package/dist/ui/composer-input.js +46 -0
  77. package/dist/ui/composer-limits.d.ts +3 -4
  78. package/dist/ui/composer-limits.js +4 -9
  79. package/dist/ui/composer-rows.d.ts +1 -1
  80. package/dist/ui/composer-rows.js +24 -137
  81. package/dist/ui/editor-navigation.d.ts +45 -0
  82. package/dist/ui/editor-navigation.js +85 -0
  83. package/dist/ui/editor-reducer.d.ts +9 -0
  84. package/dist/ui/editor-reducer.js +31 -15
  85. package/dist/ui/enter-frames.d.ts +15 -31
  86. package/dist/ui/enter-frames.js +14 -33
  87. package/dist/ui/interaction-copy.d.ts +77 -0
  88. package/dist/ui/interaction-copy.js +40 -0
  89. package/dist/ui/interaction-hints.d.ts +18 -0
  90. package/dist/ui/interaction-hints.js +92 -0
  91. package/dist/ui/layout/budget.js +1 -1
  92. package/dist/ui/layout/queue-layout.d.ts +19 -0
  93. package/dist/ui/layout/queue-layout.js +26 -0
  94. package/dist/ui/layout/status-layout.d.ts +55 -0
  95. package/dist/ui/layout/status-layout.js +109 -0
  96. package/dist/ui/layout/todo-layout.d.ts +1 -0
  97. package/dist/ui/layout/todo-layout.js +1 -1
  98. package/dist/ui/overlays/HelpOverlay.js +11 -3
  99. package/dist/ui/overlays/ModelProfileEditor.d.ts +12 -0
  100. package/dist/ui/overlays/ModelProfileEditor.js +103 -0
  101. package/dist/ui/overlays/ModelProfilePicker.d.ts +35 -0
  102. package/dist/ui/overlays/ModelProfilePicker.js +167 -0
  103. package/dist/ui/overlays/QueueOverlay.d.ts +22 -0
  104. package/dist/ui/overlays/QueueOverlay.js +79 -0
  105. package/dist/ui/overlays/SettingsScreen.d.ts +9 -2
  106. package/dist/ui/overlays/SettingsScreen.js +347 -3
  107. package/dist/ui/overlays/model-profile-state.d.ts +74 -0
  108. package/dist/ui/overlays/model-profile-state.js +119 -0
  109. package/dist/ui/run-status-row.d.ts +7 -19
  110. package/dist/ui/run-status-row.js +2 -18
  111. package/dist/ui/selection/selection-controller.d.ts +2 -2
  112. package/dist/ui/selection/selection-controller.js +58 -20
  113. package/dist/ui/transcript-text.js +20 -12
  114. package/dist/ui/use-wheel-routing.js +2 -1
  115. package/package.json +2 -2
@@ -1,95 +1,15 @@
1
- /**
2
- * The bottom status row — the one already-budgeted row above the composer, now
3
- * shared by two occupants (agent-activity-presentation §3.2.3 / D-17 / P0-1).
4
- *
5
- * THE ACTIVITY LINE IS NOT A NEW `AppShell` SLOT, AND THIS FILE IS WHY.
6
- *
7
- * `viewportRows()` (`layout/budget.ts`) is the ONE function that says how many
8
- * rows the viewport gets, and it enumerates exactly `header + toast + composer +
9
- * status`. In full-screen `AppShell`'s root is `height={frameHeight(rows)}` with
10
- * `overflow="hidden"`, the bottom chrome box is `flexShrink={0}` and the viewport
11
- * box is its only `flexShrink={1}` child — so an extra chrome row does not make
12
- * the frame taller, Yoga takes the row out of the TRANSCRIPT. Meanwhile
13
- * `viewportRows()` keeps returning the old number to `ScrollViewport`,
14
- * `selectWindow`'s spacers, `overlayMaxRows`, `popupMaxRows` and `buildTodoRailLayout`.
15
- * The transcript would draw one row shorter than every consumer believes, ONLY
16
- * WHILE A RUN IS IN FLIGHT, so the layout would shift on submit and shift back on
17
- * `agent_end`.
18
- *
19
- * `ToastStack.tsx` already legislates this for itself: *"in `fullscreen` this
20
- * ALWAYS occupies exactly one row — an empty row when there is nothing to say.
21
- * Rendering conditionally would make the viewport height (and therefore the whole
22
- * transcript) jump every time a toast appears or expires."* A run starts and ends
23
- * far more often than a toast appears and expires, so the activity line is the
24
- * STRONGER case for that rule, not an exception to it.
25
- *
26
- * Sharing the row means `budget.ts` and `AppShell.tsx` come out of this round
27
- * UNMODIFIED — and `budget.test.ts` asserts that they did (AC-8a, DoD #8).
28
- *
29
- * PRECEDENCE: toast > activity > update > blank. A toast wins because a
30
- * transient ack is a RESPONSE TO THE USER, and the row nearest the input belongs
31
- * to it. The update line is LAST because it is the only one of the three that is
32
- * PERSISTENT (cli-auto-update D-2): it loses nothing by waiting for the run to
33
- * end, whereas an activity line deferred is an activity line that never renders.
34
- * That also means a user who is actively working never sees the updater at all
35
- * until they stop, which is the whole of the silence requirement.
36
- *
37
- * THE TOAST TAKES THE ROW BUT NOT THE SPINNER
38
- * (`activity-spinner-vanishes-behind-toast`). "Toast wins" used to mean the
39
- * activity line was never mounted while an ack was up — and because `App`
40
- * suppresses the other seven animated sites for exactly as long as it BELIEVES
41
- * this row is showing one, a mid-run toast left the whole frame with ZERO
42
- * animations for its 2.5 s TTL, indistinguishable from a hung process. Steering
43
- * and plan approval both ack mid-run, so a long run hit it repeatedly.
44
- *
45
- * The row still belongs to the toast: the spinner joins it as a bare glyph, no
46
- * phrase and no clock, so `bottom-status-row.test.tsx`'s "the ack keeps its
47
- * words" assertion and the toast's own width are both untouched. A row-direction
48
- * box holding two one-row children is STILL ONE ROW, which is what the note
49
- * above actually forbids disturbing — `budget.ts` and `AppShell.tsx` stay out of
50
- * it. And because the glyph is mounted under precisely the condition `App` uses
51
- * to suppress the others, "the line is up" and "every other site is still" agree
52
- * again by construction rather than by enumeration of what can pre-empt the row.
53
- */
1
+ /** The permanent action/feedback row. StatusBar owns all activity animation. */
54
2
  import React from 'react';
55
3
  import type { Theme } from './theme.js';
56
4
  import type { Toast } from '../agent/reducer.js';
5
+ import { type ActionHintInput } from './interaction-hints.js';
57
6
  export interface BottomStatusRowProps {
58
7
  toasts: Toast[];
59
- /** `<ActivityLine>` while a run owns the screen, else `null`. */
60
- activity: React.ReactNode;
61
- /**
62
- * The bare animated spinner (`liveSpinner`), or `null` when it would be a
63
- * STATIC glyph — an ASCII terminal, or `reducedMotion`.
64
- *
65
- * IT IS NOT A SECOND MOUNT CONDITION. `activity` alone decides whether a life
66
- * signal belongs on this row; this prop only says which FORM the signal takes,
67
- * and it is read only when `activity` is non-null. So a caller cannot make the
68
- * two disagree, which is the failure mode that produced this prop.
69
- *
70
- * `null` means PREFIX NOTHING, not "prefix the still glyph". A static `·` in
71
- * front of every toast would be a visible change to ASCII terminals and to
72
- * users who asked for stillness — neither of whom ever had the dead-frame bug,
73
- * and both of whom must come out byte-identical.
74
- */
8
+ hints?: ActionHintInput;
9
+ /** Legacy assembly props are accepted but never mount an activity spinner. */
10
+ activity?: React.ReactNode;
75
11
  activityGlyph?: React.ReactElement | null;
76
- /**
77
- * `<UpdateLine>` when the updater has something to say, else `null` — AND THE
78
- * CALLER DECIDES WHICH (C-15 / cli-auto-update P0-1).
79
- *
80
- * `if (update)` below tests a REACT ELEMENT, and an element whose own render
81
- * returns `null` is still truthy: that branch would emit a box of ZERO rows,
82
- * the frame would keep its fixed height, Yoga would hand the row to the
83
- * transcript, and `viewportRows()` would go on reporting the old number to
84
- * five consumers. So `UpdateLine` is typed `React.ReactElement` and `App`
85
- * gates it with `shouldRenderUpdateLine(snapshot)`.
86
- *
87
- * OPTIONAL, because `bottom-status-row.test.tsx` and the `App.tsx` fixtures
88
- * construct this component without it and the test tree is inside
89
- * `tsconfig.test.json` (`typecheck-scope.test.ts`); a required prop turns
90
- * `npm run typecheck` red for a reason unrelated to this feature (P2-1).
91
- */
92
12
  update?: React.ReactNode;
93
13
  theme: Theme;
94
14
  }
95
- export declare function BottomStatusRow({ toasts, activity, activityGlyph, update, theme, }: BottomStatusRowProps): React.ReactElement | null;
15
+ export declare function BottomStatusRow(props: BottomStatusRowProps): React.ReactElement;
@@ -1,27 +1,12 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Box, Text } from 'ink';
3
- import { ToastStack } from './ToastStack.js';
4
- export function BottomStatusRow({ toasts, activity, activityGlyph, update, theme, }) {
5
- // A toast wins the row outright — but a run in flight keeps its spinner on it.
6
- if (toasts.length > 0) {
7
- const spinner = activity ? activityGlyph : null;
8
- // Byte-identical to the old branch whenever there is no live spinner to
9
- // carry: no run, an ASCII terminal, or `reducedMotion`.
10
- if (!spinner)
11
- return _jsx(ToastStack, { toasts: toasts, theme: theme });
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
  }
@@ -1,31 +1,14 @@
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';
18
5
  import type { TermCapabilities } from './capabilities.js';
19
6
  import { pickGlyphs } from './glyphs.js';
7
+ import type { ComposerSubmitResult } from './composer-input.js';
20
8
  import { type CommandOption } from './PromptInput.js';
21
- import { type RunActivity } from './run-status-row.js';
9
+ import type { RunActivity } from './run-status-row.js';
22
10
  import { type AgentMode } from '../agent/agent-mode.js';
23
- /**
24
- * The run status row (tui-scrollbar-edge-and-run-row §3.3): the spinner, the phrase
25
- * and the `queue / interrupt / exit` clauses on ONE row above the input box.
26
- * `App` passes it only while a run is in flight AND the idle hint row would have
27
- * been shown, so enabling it never changes the footer's height.
28
- */
11
+ /** Legacy caller shape retained for source compatibility; Composer no longer mounts it. */
29
12
  export interface RunRowProps {
30
13
  activity: RunActivity;
31
14
  /** The row is in the viewport with no overlay: draw the animated spinner. */
@@ -34,9 +17,11 @@ export interface RunRowProps {
34
17
  rowRef?: React.RefObject<DOMElement>;
35
18
  }
36
19
  export interface ComposerProps {
37
- /** Present only while the run status row replaces the hint row below the input. */
20
+ /** Compatibility prop; activity is now rendered by the global StatusBar. */
38
21
  runRow?: RunRowProps | null;
39
22
  cols?: number;
23
+ deleteDisambiguated?: boolean;
24
+ onCompletionContextChange?: (context: 'none' | 'slash' | 'file') => void;
40
25
  cursorVisible?: boolean;
41
26
  onInteraction?: () => void;
42
27
  measureRef?: React.RefObject<DOMElement>;
@@ -51,48 +36,20 @@ export interface ComposerProps {
51
36
  cwd: string;
52
37
  /** Dropped on short terminals to buy back a row (§5.4). */
53
38
  showHint: boolean;
54
- /** Lifetime submissions; drives progressive disclosure of the idle hint. */
39
+ /** Compatibility prop; submission count no longer hides essential actions. */
55
40
  submitCount: number;
56
- /** User opt-out (`--no-hints` / `hints: false`). */
41
+ /** Compatibility prop; App owns secondary teaching-hint visibility. */
57
42
  hintsEnabled: boolean;
58
- /** Mirror of the controller's effective mode; drives the chip and the border. */
43
+ /** Mirror of the controller's effective mode; drives the border. */
59
44
  agentMode: AgentMode;
60
- /**
61
- * Which key this hint should NAME for the mode toggle
62
- * (shift-tab-mode-toggle-still-dead-on-windows, C3-1).
63
- *
64
- * A PROP RATHER THAN A CONSTANT because on some consoles the constant is a
65
- * lie. This row used to render `shift+tab` unconditionally, with no link to
66
- * the capability check three files away, so the machines where that key is
67
- * swallowed were the ones being told to press it - permanently, on every
68
- * frame, while the one warning they got scrolled out of the transcript
69
- * minutes earlier (analysis section 3.7). Defaults to the primary key, which
70
- * is what every unaffected machine renders.
71
- */
45
+ /** Compatibility prop; the global action row names the effective mode key. */
72
46
  modeToggleKey?: string;
73
47
  popupMaxRows?: number;
74
48
  popupMaxHeight?: number;
75
49
  onPopupRowsChange?: (rows: number) => void;
76
- /**
77
- * Rows of newer output below the viewport, forwarded to the input row's chip
78
- * (tui-selection-and-scroll-follow §4.2 / G7).
79
- *
80
- * IT IS STATE, NOT A FADEABLE HINT, and it therefore does NOT pass through the
81
- * `showHint` / `hintsEnabled` gate below. Those two govern the teaching row —
82
- * which retires keys the user has demonstrably learned and is dropped outright
83
- * on a short terminal — and "you are 12 rows behind the newest output" is
84
- * neither learnable nor optional. Same argument that keeps `esc abort`
85
- * un-faded in `hintText`.
86
- */
50
+ /** Rows of newer output below the viewport, forwarded to the input's scroll chip. */
87
51
  scrolledLines?: number;
88
- /**
89
- * Live background services (background-service-supervision §3.8), 0 when none.
90
- *
91
- * IT IS STATE, NOT A FADEABLE HINT, exactly like `scrolledLines` above: the
92
- * clause it adds is the only visible way to stop something, and `hintText`
93
- * below keeps it out of the faded form for the same reason `esc abort` is kept
94
- * there.
95
- */
52
+ /** Compatibility prop; the global status and action rows show live services. */
96
53
  services?: number;
97
54
  /**
98
55
  * The composer's DRAFT row count, forwarded upward (tui-paste-handling
@@ -109,30 +66,15 @@ export interface ComposerProps {
109
66
  onNotice?: (level: 'warn' | 'error', text: string) => void;
110
67
  theme: Theme;
111
68
  caps: TermCapabilities;
112
- onSubmit: (text: string) => void;
69
+ onSubmit: (text: string) => ComposerSubmitResult;
113
70
  onHelp?: () => void;
114
71
  }
115
- /**
116
- * The running-state hint as separate clauses, most urgent first.
117
- *
118
- * `hintText` joins them (its output is pinned by AC-43), and the run status row
119
- * consumes the SAME array so it can drop whole clauses from the tail on a narrow
120
- * terminal instead of cutting `interrupt` in half. One source, two consumers.
121
- */
72
+ /** Legacy helper retained for consumers and contract tests; not rendered by Composer. */
122
73
  export declare function runningHintClauses(opts: {
123
74
  glyphs: ReturnType<typeof pickGlyphs>;
124
75
  services: number;
125
76
  }): string[];
126
- /**
127
- * Build the hint row.
128
- *
129
- * The `running` branch is NEVER abbreviated, no matter how experienced the user
130
- * is. Once the status bar's hint cluster was removed, the composer became the
131
- * only place that names the abort key — and fading that out after eight
132
- * submissions would leave a long-running task with no visible way to stop it.
133
- * Progressive disclosure is for teaching discoverable things, not for retiring
134
- * an emergency exit (§4.6 / A-14 / M-10b).
135
- */
77
+ /** Compatibility formatter. Product hints use interaction-hints.ts. */
136
78
  declare function hintText(opts: {
137
79
  running: boolean;
138
80
  submitCount: number;
@@ -142,15 +84,7 @@ declare function hintText(opts: {
142
84
  /** Live background services; 0 leaves both rows byte-identical to today's. */
143
85
  services: number;
144
86
  }): string;
145
- /**
146
- * `hintText`, for the interrupt-ladder tests (AC-43).
147
- *
148
- * EXPORTED UNDER A TEST-FACING NAME rather than exporting `hintText` itself, so
149
- * the private helper stays private in the module graph and nothing in `src/`
150
- * grows a second caller by accident. The row is the ONLY visible way to stop a
151
- * process the agent left running, so it is worth asserting on directly rather
152
- * than fishing the string out of a rendered frame.
153
- */
87
+ /** Compatibility export for existing hint contract tests. */
154
88
  export declare const hintTextForTest: typeof hintText;
155
- export declare function Composer({ runRow, cols, cursorVisible, onInteraction, measureRef, isActive, reducedMotion, onEscape, onEscapeDismiss, running, history, commands, cwd, showHint, submitCount, hintsEnabled, agentMode, services, modeToggleKey, popupMaxRows, popupMaxHeight, onPopupRowsChange, scrolledLines, onDraftRows, onNotice, theme, caps, onSubmit, onHelp, }: ComposerProps): React.ReactElement;
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;
156
90
  export {};
@@ -1,80 +1,26 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
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, Text } from 'ink';
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 { ModeChip, modeChipCols } from './ModeChip.js';
23
- import { ActivityLabel, resolveActivityLabel } from './ActivityLine.js';
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 RunRow({ runRow, cols, services, agentMode, theme, caps, }) {
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
- const glyphs = pickGlyphs(caps);
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
  }
@@ -25,6 +25,7 @@ import { pickGlyphs } from './glyphs.js';
25
25
  import { type EditIntent } from '../input/keymap.js';
26
26
  import { type Suggestion } from './AutocompletePopup.js';
27
27
  import { type EditorState, type InputSegment } from './editor-reducer.js';
28
+ import { type ComposerSubmitResult } from './composer-input.js';
28
29
  import type { AgentMode } from '../agent/agent-mode.js';
29
30
  export interface CommandOption {
30
31
  name: string;
@@ -51,7 +52,10 @@ interface PromptInputProps {
51
52
  /** Total menu rows, including borders and the overflow hint. */
52
53
  popupMaxHeight?: number;
53
54
  onPopupRowsChange?: (rows: number) => void;
54
- onSubmit: (text: string) => void;
55
+ onCompletionContextChange?: (context: 'none' | 'slash' | 'file') => void;
56
+ /** True only when the stdin filter normalized DEL before Ink parsed it. */
57
+ deleteDisambiguated?: boolean;
58
+ onSubmit: (text: string) => ComposerSubmitResult;
55
59
  onHelp?: () => void;
56
60
  onEscape?: () => void;
57
61
  onEscapeDismiss?: () => void;
@@ -153,5 +157,5 @@ export declare function fileTokenAt(buffer: string, cursor: number): {
153
157
  end: number;
154
158
  query: string;
155
159
  } | null;
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;
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;
157
161
  export {};