@aragon-agent/cli 0.6.20 → 0.6.21
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/dist/ui/App.js +66 -2
- package/dist/ui/Composer.d.ts +14 -1
- package/dist/ui/Composer.js +6 -3
- package/dist/ui/PromptInput.d.ts +10 -1
- package/dist/ui/PromptInput.js +11 -1
- package/dist/ui/editor-reducer.js +10 -5
- package/dist/ui/interaction-copy.d.ts +1 -0
- package/dist/ui/interaction-copy.js +1 -0
- package/dist/ui/interaction-hints.d.ts +2 -0
- package/dist/ui/interaction-hints.js +1 -1
- package/dist/ui/layout/status-layout.js +1 -0
- package/dist/ui/overlays/HelpOverlay.js +4 -1
- package/dist/ui/overlays/ModelProfilePicker.js +12 -1
- package/dist/ui/status-feedback.d.ts +3 -1
- package/dist/ui/status-feedback.js +3 -1
- package/package.json +2 -2
package/dist/ui/App.js
CHANGED
|
@@ -128,6 +128,13 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
128
128
|
const { stdout } = useStdout();
|
|
129
129
|
const size = useTerminalSize();
|
|
130
130
|
const [draftRows, setDraftRows] = useState(1);
|
|
131
|
+
// Ctrl+C draft ladder (ctrl-c-clear-draft-exit). The nonce ticks when
|
|
132
|
+
// the armed ladder spends its clear rung; the flag marks "clear spent"
|
|
133
|
+
// so the render-time feedback already reads the exit wording at the
|
|
134
|
+
// nonce render (the async clear lands a frame later - see the timing
|
|
135
|
+
// invariant beside the ladder itself).
|
|
136
|
+
const [draftClearNonce, setDraftClearNonce] = useState(0);
|
|
137
|
+
const [draftClearedByCtrlC, setDraftClearedByCtrlC] = useState(false);
|
|
131
138
|
const [popupRows, setPopupRows] = useState(0);
|
|
132
139
|
const [statusExpanded, setStatusExpanded] = useState(false);
|
|
133
140
|
const dimensions = buildFrameBudget({ ...size, draftRows, popupRows, statusExpanded });
|
|
@@ -1145,6 +1152,21 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1145
1152
|
const onDraftRows = useCallback((next) => {
|
|
1146
1153
|
setDraftRows(previous => previous === next ? previous : next);
|
|
1147
1154
|
}, []);
|
|
1155
|
+
/**
|
|
1156
|
+
* Draft presence, written to a REF on purpose (ctrl-c-clear-draft-exit):
|
|
1157
|
+
* promoting it to state would re-render the transcript on the first
|
|
1158
|
+
* keystroke of every message - the exact cost Composer.tsx documents
|
|
1159
|
+
* for keeping `hasDraft` local. The ladder reads the ref inside its key
|
|
1160
|
+
* handler, where freshness is guaranteed; the render-time feedback
|
|
1161
|
+
* uses `hasDraftRef.current && !draftClearedByCtrlC` instead.
|
|
1162
|
+
*/
|
|
1163
|
+
const hasDraftRef = useRef(false);
|
|
1164
|
+
const onDraftPresence = useCallback((hasDraft) => {
|
|
1165
|
+
hasDraftRef.current = hasDraft;
|
|
1166
|
+
// A draft that re-appears after a ladder clear re-arms the clear rung.
|
|
1167
|
+
if (hasDraft)
|
|
1168
|
+
setDraftClearedByCtrlC(false);
|
|
1169
|
+
}, []);
|
|
1148
1170
|
// --- Auto-update bridge (cli-auto-update section 3.8 / 6.3). ------------
|
|
1149
1171
|
//
|
|
1150
1172
|
// TWO ARRIVAL ORDERS, AND BOTH HAPPEN. The service is built by a dynamic
|
|
@@ -1743,7 +1765,43 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1743
1765
|
toast('warn', `Stopping ${live} service${live === 1 ? '' : 's'}.`);
|
|
1744
1766
|
return;
|
|
1745
1767
|
}
|
|
1768
|
+
//
|
|
1769
|
+
// RENDER-TIMING INVARIANT (part of this fix, not an implementation
|
|
1770
|
+
// detail): the status feedback is evaluated during render, but
|
|
1771
|
+
// `hasDraftRef` is a ref that does not itself trigger one. Every
|
|
1772
|
+
// rung still lands exactly one render - arm bumps `metricsClock`,
|
|
1773
|
+
// the clear rung bumps `draftClearNonce`/`draftClearedByCtrlC` - and
|
|
1774
|
+
// the async clear (nonce -> effect -> 'clear' -> the onDraftChange
|
|
1775
|
+
// transition) refills the ref within the same tick, far faster than
|
|
1776
|
+
// a human's next press. `draftClearedByCtrlC` exists because the
|
|
1777
|
+
// nonce render happens BEFORE that chain: without it the feedback
|
|
1778
|
+
// would still read hasDraft=true and repeat the clear prompt after
|
|
1779
|
+
// clearing. If a future rung ever becomes a pure ref write with no
|
|
1780
|
+
// state change, its feedback text will silently go stale.
|
|
1781
|
+
//
|
|
1782
|
+
// TWO ACCEPTED EDGES (facts drive the ladder, by design):
|
|
1783
|
+
// - arm (empty, "to exit") then PASTE a draft within 1.5s: the
|
|
1784
|
+
// second press clears instead of exiting - the cleared text is
|
|
1785
|
+
// still in the clipboard, so nothing is actually lost.
|
|
1786
|
+
// - arm (draft, "to clear") then Ctrl+U kills the draft by hand:
|
|
1787
|
+
// the second press falls to the exit branch - with nothing left
|
|
1788
|
+
// to clear, the next rung owns the press (tmux/htop behave the
|
|
1789
|
+
// same way).
|
|
1790
|
+
//
|
|
1791
|
+
// RUNG TWO/TREE, composed from two existing facts: armed x
|
|
1792
|
+
// hasDraft. No new counter, no new timer - clearing the draft
|
|
1793
|
+
// flips hasDraftRef to false, so the NEXT press naturally lands on
|
|
1794
|
+
// the exit branch: draft present -> x2 clears, x3 exits; empty ->
|
|
1795
|
+
// x2 exits, byte-identical to the previous behaviour.
|
|
1746
1796
|
if (ctrlCArmed.current) {
|
|
1797
|
+
if (hasDraftRef.current) {
|
|
1798
|
+
setDraftClearNonce(value => value + 1);
|
|
1799
|
+
setDraftClearedByCtrlC(true);
|
|
1800
|
+
// Stays armed and the 1500ms clock keeps running: the clear
|
|
1801
|
+
// rung must not re-arm the whole ladder (that would make
|
|
1802
|
+
// exiting a draft x4 presses).
|
|
1803
|
+
return;
|
|
1804
|
+
}
|
|
1747
1805
|
doExit();
|
|
1748
1806
|
return;
|
|
1749
1807
|
}
|
|
@@ -1755,6 +1813,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1755
1813
|
ctrlCTimer.current = setTimeout(() => {
|
|
1756
1814
|
ctrlCTimer.current = null;
|
|
1757
1815
|
ctrlCArmed.current = false;
|
|
1816
|
+
setDraftClearedByCtrlC(false);
|
|
1758
1817
|
setMetricsClock(value => value + 1);
|
|
1759
1818
|
}, 1500);
|
|
1760
1819
|
return;
|
|
@@ -2135,6 +2194,10 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2135
2194
|
const feedbackProjection = projectStatusFeedback({
|
|
2136
2195
|
interactionPhase: interactionPhase.current, interruptPhase: interruptGesture.current.phase,
|
|
2137
2196
|
completion, overlay, copyState, copyResult, ctrlCArmed: ctrlCArmed.current,
|
|
2197
|
+
// The flag, not the raw ref: the nonce render precedes the async
|
|
2198
|
+
// clear, so the ref alone would repeat the clear wording (see the
|
|
2199
|
+
// render-timing invariant beside the ladder).
|
|
2200
|
+
hasDraft: hasDraftRef.current && !draftClearedByCtrlC,
|
|
2138
2201
|
selectionPending: selectionController?.hasPendingSelection() ?? false, liveServices,
|
|
2139
2202
|
toast: latestToast ? { text: latestToast.text,
|
|
2140
2203
|
level: latestToast.level === 'success' ? 'info' : latestToast.level } : undefined,
|
|
@@ -2159,10 +2222,11 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2159
2222
|
hints: { interactionPhase: interactionPhase.current, cols, completion, overlay,
|
|
2160
2223
|
selectionPending: selectionController?.hasPendingSelection() ?? false,
|
|
2161
2224
|
copyInFlight: copyState.busy, copyCleanupPending: copyState.cleanupPending,
|
|
2162
|
-
|
|
2225
|
+
hasDraft: hasDraftRef.current, services: liveServices,
|
|
2226
|
+
hintsEnabled: cfg.hints, modeToggleKey,
|
|
2163
2227
|
interruptPhase: interruptGesture.current.phase,
|
|
2164
2228
|
updateAvailable: !!updateSnapshot && shouldRenderUpdateLine(updateSnapshot) } });
|
|
2165
|
-
const composer = (_jsx(Composer, { deleteDisambiguated: terminal?.deleteDisambiguated ?? false, onCompletionContextChange: setCompletion, cols: frameBudget.composerCols, terminalRows: rows, statusExpanded: statusExpanded, onInteraction: returnToComposer, reducedMotion: cfg.reducedMotion ?? false, onEscape: handleComposerEscape, onEscapeDismiss: cancelConfirmation, isActive: overlay === null && !tooSmall, running: state.status === 'running', history: promptHistory, commands: commandOptions, cwd: controller.getCwd(), showHint: false, submitCount: cfg.submitCount, hintsEnabled: cfg.hints, agentMode: state.agentMode, services: liveServices, modeToggleKey: modeToggleKey, popupMaxRows: popupMaxRows, popupMaxHeight: frameBudget.popupMaxHeight, onPopupRowsChange: onPopupRowsChange, onDraftRows: onDraftRows, onNotice: notify, theme: theme, caps: caps, onSubmit: handleSubmit, onHelp: () => dispatch({ type: 'setOverlay', overlay: 'help' }) }));
|
|
2229
|
+
const composer = (_jsx(Composer, { deleteDisambiguated: terminal?.deleteDisambiguated ?? false, onCompletionContextChange: setCompletion, cols: frameBudget.composerCols, terminalRows: rows, statusExpanded: statusExpanded, onInteraction: returnToComposer, reducedMotion: cfg.reducedMotion ?? false, onEscape: handleComposerEscape, onEscapeDismiss: cancelConfirmation, isActive: overlay === null && !tooSmall, running: state.status === 'running', history: promptHistory, commands: commandOptions, cwd: controller.getCwd(), showHint: false, submitCount: cfg.submitCount, hintsEnabled: cfg.hints, agentMode: state.agentMode, services: liveServices, modeToggleKey: modeToggleKey, popupMaxRows: popupMaxRows, popupMaxHeight: frameBudget.popupMaxHeight, onPopupRowsChange: onPopupRowsChange, onDraftRows: onDraftRows, onDraftPresence: onDraftPresence, draftClearNonce: draftClearNonce, onNotice: notify, theme: theme, caps: caps, onSubmit: handleSubmit, onHelp: () => dispatch({ type: 'setOverlay', overlay: 'help' }) }));
|
|
2166
2230
|
const team = state.team ? (_jsx(TeamPanel, { snapshot: state.team, layout: teamLayout, rows: rows, cols: contentCols, reducedMotion: viewReducedMotion, theme: theme, caps: caps })) : null;
|
|
2167
2231
|
const rail = showRail && state.todos ? (_jsx(TodoPanel, { snapshot: state.todos, width: railWidth, rows: railRows, running: running, reducedMotion: viewReducedMotion, theme: theme, caps: caps })) : null;
|
|
2168
2232
|
const viewport = (_jsxs(ScrollViewport, { active: overlay === null && !tooSmall, overlay: overlayNode, footer: _jsxs(_Fragment, { children: [team, _jsx(QueuePanel, { layout: queueLayout, theme: theme })] }), rail: rail, scrollbar: terminal?.scrollbar, resetKey: scrollResetKey, intent: scrollIntent, pinToBottomNonce: pinToBottomNonce, onScrolledLinesChange: setScrolledLines, onViewportShiftChange: onViewportShiftChange, onGeometryChange: onGeometryChange, geometryKey: `${rows}:${cols}:${viewportBudget}:${frameBudget.composerSlotRows}:${frameBudget.statusRows}:${contentCols}:${overlay}:${tooSmall}`, tailRowsRef: tailSink, hold: selectionHold, resumeMs: cfg.scrollResumeMs, showScrollIndicator: true, cols: contentCols, theme: theme, caps: caps, children: [opener, _jsx(TranscriptList, { entries: transcriptEntries, expandedToolIds: state.expandedToolIds, thinkingVisible: state.thinkingVisible, reducedMotion: viewReducedMotion, density: density, nowSec: nowSec, theme: theme, caps: caps, windowSize: transcriptWindow, cols: contentCols, heights: heights, mountedSink: mountedCount, tailSink: tailSink })] }));
|
package/dist/ui/Composer.d.ts
CHANGED
|
@@ -64,6 +64,19 @@ export interface ComposerProps {
|
|
|
64
64
|
* character, which is what keeps the re-render rate the same (R-6).
|
|
65
65
|
*/
|
|
66
66
|
onDraftRows?: (rows: number) => void;
|
|
67
|
+
/**
|
|
68
|
+
* Draft PRESENCE, forwarded upward for the Ctrl+C ladder
|
|
69
|
+
* (ctrl-c-clear-draft-exit). App stores it in a ref only - no state,
|
|
70
|
+
* so the transcript does not re-render on the first keystroke of a
|
|
71
|
+
* message (the same cost the comment above documents).
|
|
72
|
+
*/
|
|
73
|
+
onDraftPresence?: (hasDraft: boolean) => void;
|
|
74
|
+
/**
|
|
75
|
+
* Downward half of the same channel: ticks when App asks the editor
|
|
76
|
+
* to clear the draft (Ctrl+C x2 while drafting). Passed through to
|
|
77
|
+
* PromptInput unchanged.
|
|
78
|
+
*/
|
|
79
|
+
draftClearNonce?: number;
|
|
67
80
|
/** Paste refusals from `PromptInput`, routed to `App`'s notice dispatch. */
|
|
68
81
|
onNotice?: (level: 'warn' | 'error', text: string) => void;
|
|
69
82
|
theme: Theme;
|
|
@@ -88,5 +101,5 @@ declare function hintText(opts: {
|
|
|
88
101
|
}): string;
|
|
89
102
|
/** Compatibility export for existing hint contract tests. */
|
|
90
103
|
export declare const hintTextForTest: typeof hintText;
|
|
91
|
-
export declare function Composer({ deleteDisambiguated, onCompletionContextChange, cols, terminalRows, statusExpanded, cursorVisible, onInteraction, measureRef, isActive, reducedMotion, onEscape, onEscapeDismiss, running, history, commands, cwd, agentMode, popupMaxRows, popupMaxHeight, onPopupRowsChange, onDraftRows, onNotice, theme, caps, onSubmit, onHelp, }: ComposerProps): React.ReactElement;
|
|
104
|
+
export declare function Composer({ deleteDisambiguated, onCompletionContextChange, cols, terminalRows, statusExpanded, cursorVisible, onInteraction, measureRef, isActive, reducedMotion, onEscape, onEscapeDismiss, running, history, commands, cwd, agentMode, popupMaxRows, popupMaxHeight, onPopupRowsChange, onDraftRows, onDraftPresence, draftClearNonce, onNotice, theme, caps, onSubmit, onHelp, }: ComposerProps): React.ReactElement;
|
|
92
105
|
export {};
|
package/dist/ui/Composer.js
CHANGED
|
@@ -36,12 +36,15 @@ function hintText(opts) {
|
|
|
36
36
|
}
|
|
37
37
|
/** Compatibility export for existing hint contract tests. */
|
|
38
38
|
export const hintTextForTest = hintText;
|
|
39
|
-
export function Composer({ deleteDisambiguated, onCompletionContextChange, cols, terminalRows, statusExpanded, cursorVisible, onInteraction, measureRef, isActive, reducedMotion, onEscape, onEscapeDismiss, running, history, commands, cwd, agentMode, popupMaxRows, popupMaxHeight, onPopupRowsChange, onDraftRows, onNotice, theme, caps, onSubmit, onHelp, }) {
|
|
39
|
+
export function Composer({ deleteDisambiguated, onCompletionContextChange, cols, terminalRows, statusExpanded, cursorVisible, onInteraction, measureRef, isActive, reducedMotion, onEscape, onEscapeDismiss, running, history, commands, cwd, agentMode, popupMaxRows, popupMaxHeight, onPopupRowsChange, onDraftRows, onDraftPresence, draftClearNonce, onNotice, theme, caps, onSubmit, onHelp, }) {
|
|
40
40
|
const [hasDraft, setHasDraft] = useState(false);
|
|
41
41
|
const onDraftChange = useCallback((next) => {
|
|
42
42
|
setHasDraft(next.hasDraft);
|
|
43
|
+
// Presence FIRST: the App ref must be current before the row-count
|
|
44
|
+
// report below can trigger the App render that reads it.
|
|
45
|
+
onDraftPresence?.(next.hasDraft);
|
|
43
46
|
onDraftRows?.(next.rows);
|
|
44
|
-
}, [onDraftRows]);
|
|
47
|
+
}, [onDraftPresence, onDraftRows]);
|
|
45
48
|
// Plan mode inserts ONE branch, above `draft` and below `running`. The
|
|
46
49
|
// ordering is load-bearing: an in-flight run's border must stay `toolRunning`,
|
|
47
50
|
// because "is it running" is more urgent than "which mode".
|
|
@@ -54,5 +57,5 @@ export function Composer({ deleteDisambiguated, onCompletionContextChange, cols,
|
|
|
54
57
|
: hasDraft
|
|
55
58
|
? theme.focusBorder ?? theme.primary
|
|
56
59
|
: theme.idleBorder ?? theme.border;
|
|
57
|
-
return (_jsx(Box, { ref: measureRef, width: cols, flexDirection: "column", flexShrink: 0, children: _jsx(PromptInput, { deleteDisambiguated: deleteDisambiguated, onCompletionContextChange: onCompletionContextChange, cols: cols, terminalRows: terminalRows, statusExpanded: statusExpanded, 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 }) }));
|
|
60
|
+
return (_jsx(Box, { ref: measureRef, width: cols, flexDirection: "column", flexShrink: 0, children: _jsx(PromptInput, { deleteDisambiguated: deleteDisambiguated, onCompletionContextChange: onCompletionContextChange, cols: cols, terminalRows: terminalRows, statusExpanded: statusExpanded, 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, draftClearNonce: draftClearNonce, onNotice: onNotice }) }));
|
|
58
61
|
}
|
package/dist/ui/PromptInput.d.ts
CHANGED
|
@@ -93,6 +93,15 @@ interface PromptInputProps {
|
|
|
93
93
|
* component is the only place that holds both facts.
|
|
94
94
|
*/
|
|
95
95
|
onNotice?: (level: 'warn' | 'error', text: string) => void;
|
|
96
|
+
/**
|
|
97
|
+
* App-owned draft clearing (ctrl-c-clear-draft-exit): the second
|
|
98
|
+
* Ctrl+C of the armed ladder asks the editor to drop the draft. A
|
|
99
|
+
* NUMBER, not a boolean, so repeated requests stay distinguishable
|
|
100
|
+
* and the effect below never re-fires for an unchanged value.
|
|
101
|
+
* `'clear'` returns the editor to INITIAL_EDITOR_STATE, releasing
|
|
102
|
+
* paste payloads with it.
|
|
103
|
+
*/
|
|
104
|
+
draftClearNonce?: number;
|
|
96
105
|
scrolledLines?: number;
|
|
97
106
|
}
|
|
98
107
|
export interface ScrollChip {
|
|
@@ -159,5 +168,5 @@ export declare function fileTokenAt(buffer: string, cursor: number): {
|
|
|
159
168
|
end: number;
|
|
160
169
|
query: string;
|
|
161
170
|
} | null;
|
|
162
|
-
export declare function PromptInput({ cols: colsProp, terminalRows: terminalRowsProp, statusExpanded, cursorVisible, onInteraction, isActive, running, history, commands, cwd, theme, caps, popupMaxRows, popupMaxHeight, onPopupRowsChange, onCompletionContextChange, deleteDisambiguated, agentMode, onSubmit, onHelp, onEscape, onEscapeDismiss, reducedMotion, borderColor, onDraftChange, onNotice, }: PromptInputProps): React.ReactElement;
|
|
171
|
+
export declare function PromptInput({ cols: colsProp, terminalRows: terminalRowsProp, statusExpanded, cursorVisible, onInteraction, isActive, running, history, commands, cwd, theme, caps, popupMaxRows, popupMaxHeight, onPopupRowsChange, onCompletionContextChange, deleteDisambiguated, agentMode, onSubmit, onHelp, onEscape, onEscapeDismiss, reducedMotion, borderColor, onDraftChange, draftClearNonce, onNotice, }: PromptInputProps): React.ReactElement;
|
|
163
172
|
export {};
|
package/dist/ui/PromptInput.js
CHANGED
|
@@ -244,7 +244,7 @@ function isControlSeq(input) {
|
|
|
244
244
|
// ---------------------------------------------------------------------------
|
|
245
245
|
// Component
|
|
246
246
|
// ---------------------------------------------------------------------------
|
|
247
|
-
export function PromptInput({ cols: colsProp, terminalRows: terminalRowsProp, statusExpanded = false, cursorVisible = true, onInteraction, isActive, running, history, commands, cwd, theme, caps, popupMaxRows, popupMaxHeight, onPopupRowsChange, onCompletionContextChange, deleteDisambiguated = false, agentMode, onSubmit, onHelp, onEscape, onEscapeDismiss, reducedMotion = false, borderColor, onDraftChange, onNotice, }) {
|
|
247
|
+
export function PromptInput({ cols: colsProp, terminalRows: terminalRowsProp, statusExpanded = false, cursorVisible = true, onInteraction, isActive, running, history, commands, cwd, theme, caps, popupMaxRows, popupMaxHeight, onPopupRowsChange, onCompletionContextChange, deleteDisambiguated = false, agentMode, onSubmit, onHelp, onEscape, onEscapeDismiss, reducedMotion = false, borderColor, onDraftChange, draftClearNonce, onNotice, }) {
|
|
248
248
|
const { stdout } = useStdout();
|
|
249
249
|
const [editor, dispatch] = useReducer(editorReducer, INITIAL_EDITOR_STATE);
|
|
250
250
|
const { buffer, cursor, historyIndex, dismissed, sel } = editor;
|
|
@@ -373,6 +373,16 @@ export function PromptInput({ cols: colsProp, terminalRows: terminalRowsProp, st
|
|
|
373
373
|
lastReportedDraft.current = { hasDraft, rows: draftRowCount };
|
|
374
374
|
onDraftChange({ hasDraft, rows: draftRowCount });
|
|
375
375
|
}, [hasDraft, draftRowCount, onDraftChange]);
|
|
376
|
+
// --- App-requested draft clear (ctrl-c-clear-draft-exit). --------
|
|
377
|
+
// The NONCE, not the buffer, is the dependency: App cannot reach this
|
|
378
|
+
// reducer directly, and keying on content would miss back-to-back
|
|
379
|
+
// requests that land on the same (empty) buffer. `'clear'` is
|
|
380
|
+
// idempotent, so a StrictMode double-invoked effect is harmless.
|
|
381
|
+
useEffect(() => {
|
|
382
|
+
if (draftClearNonce === undefined || draftClearNonce <= 0)
|
|
383
|
+
return;
|
|
384
|
+
dispatch({ type: 'clear' });
|
|
385
|
+
}, [draftClearNonce]);
|
|
376
386
|
// --- Mutators. -----------------------------------------------------------
|
|
377
387
|
// ONE DISPATCH EACH, and that is the rule §3.4 asks a reviewer to check line
|
|
378
388
|
// by line. Two dispatches from one key would put the legacy root back to two
|
|
@@ -160,17 +160,22 @@ export function editorReducer(state, action) {
|
|
|
160
160
|
case 'recall':
|
|
161
161
|
// `historyIndex` is SET, not reset, which is why recall cannot reuse
|
|
162
162
|
// `replace` — the two differ in exactly the field that keeps the walk
|
|
163
|
-
// alive. `dismissed`
|
|
164
|
-
//
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
//
|
|
163
|
+
// alive. `dismissed` is forced TRUE (history-recall-autocomplete-popup):
|
|
164
|
+
// recalled content is not a query the user is typing, so the suggestion
|
|
165
|
+
// popup must not appear over it - while it is visible it owns Up/Down and
|
|
166
|
+
// the second Up moves the highlight instead of stepping history. Any
|
|
167
|
+
// buffer edit (insert / backspace / replace / input ...) re-arms the
|
|
168
|
+
// popup through DRAFT_FLAGS, so typing over a recall restores them. The
|
|
169
|
+
// same gate covers the `@file` popup, which shares `dismissed`.
|
|
170
|
+
// `sel` is still LEFT ALONE: invisible while dismissed, re-homed by the
|
|
171
|
+
// next buffer change; touching it would only widen the diff (K-7 note).
|
|
168
172
|
return withPrune({
|
|
169
173
|
...state,
|
|
170
174
|
buffer: action.buffer,
|
|
171
175
|
cursor: snapGrapheme(action.buffer, action.cursor),
|
|
172
176
|
preferredVisualColumn: undefined,
|
|
173
177
|
historyIndex: action.historyIndex,
|
|
178
|
+
dismissed: true,
|
|
174
179
|
});
|
|
175
180
|
case 'select':
|
|
176
181
|
return state.sel === action.sel ? state : { ...state, sel: action.sel };
|
|
@@ -68,6 +68,7 @@ export declare const interactionCopy: {
|
|
|
68
68
|
readonly confirm: "Enter confirm";
|
|
69
69
|
readonly stopServices: "Ctrl+C stop services";
|
|
70
70
|
readonly exit: "Ctrl+C x2 exit";
|
|
71
|
+
readonly exitDraft: "Ctrl+C x2 clear input, x3 exit";
|
|
71
72
|
readonly help: "/help";
|
|
72
73
|
readonly shiftNewline: "Shift+Enter newline needs terminal support";
|
|
73
74
|
readonly mode: "Shift+Tab switch mode";
|
|
@@ -26,6 +26,7 @@ export const interactionCopy = {
|
|
|
26
26
|
close: 'Esc close', closeCompletion: 'Esc menu', executeCommand: 'Enter command',
|
|
27
27
|
complete: 'Tab complete', select: 'Up/Down select', scroll: 'PgUp/PgDn scroll',
|
|
28
28
|
confirm: 'Enter confirm', stopServices: 'Ctrl+C stop services', exit: 'Ctrl+C x2 exit',
|
|
29
|
+
exitDraft: 'Ctrl+C x2 clear input, x3 exit',
|
|
29
30
|
help: '/help', shiftNewline: 'Shift+Enter newline needs terminal support',
|
|
30
31
|
mode: 'Shift+Tab switch mode', update: '/update',
|
|
31
32
|
idlePlaceholder: 'Ask a question or describe a task...', runningPlaceholder: 'Add a follow-up...',
|
|
@@ -17,6 +17,8 @@ export interface ActionHintInput {
|
|
|
17
17
|
updateAvailable?: boolean;
|
|
18
18
|
exitHint?: string;
|
|
19
19
|
modeToggleKey?: string;
|
|
20
|
+
/** Draft present: the exit clause becomes the clear-then-exit ladder. */
|
|
21
|
+
hasDraft?: boolean;
|
|
20
22
|
}
|
|
21
23
|
export declare function escapeClause(input: ActionHintInput): string;
|
|
22
24
|
export declare function clipStatusText(text: string, cells: number): string;
|
|
@@ -71,7 +71,7 @@ export function buildActionClauses(input) {
|
|
|
71
71
|
if (!feedback && input.hintsEnabled !== false && !input.overlay) {
|
|
72
72
|
if (input.updateAvailable)
|
|
73
73
|
add(copy.update);
|
|
74
|
-
add(copy.exit);
|
|
74
|
+
add(input.hasDraft ? copy.exitDraft : copy.exit);
|
|
75
75
|
add((input.modeToggleKey ?? 'Shift+Tab') + ' ' + copy.modeLabel);
|
|
76
76
|
add(copy.help);
|
|
77
77
|
}
|
|
@@ -83,6 +83,7 @@ function feedbackLabel(input, form) {
|
|
|
83
83
|
const labels = {
|
|
84
84
|
confirm: ['Awaiting confirmation', 'Ask', 'Ask'], stopping: ['Esc stop', 'EscS', 'EscS'],
|
|
85
85
|
'force-stop': ['Esc force', 'EscF', 'EscF'], exit: ['^C exit', '^Cexit', '^C'],
|
|
86
|
+
'clear-input': ['^C clear', '^Cclear', '^C'],
|
|
86
87
|
copying: ['Copying', 'Copy', 'Copy'], copied: ['Copied', 'Copied', 'Copy'],
|
|
87
88
|
'copy-sent': ['Copy sent', 'Sent', 'Sent'], 'copy-cleanup': ['Copy cleanup', 'Err', 'Err'],
|
|
88
89
|
'copy-error': ['Copy err', 'Err', 'Err'], notice: ['Notice', 'Notice', 'Note'],
|
|
@@ -17,7 +17,10 @@ function keyRows(times) {
|
|
|
17
17
|
// either of them is a key the affected user never learns about, which is the
|
|
18
18
|
// one-shot notice's failure repeated with a different switch.
|
|
19
19
|
['Shift+Tab / Ctrl+P', 'Toggle plan mode (same as /plan)'],
|
|
20
|
-
|
|
20
|
+
// The ladder clears a draft first (ctrl-c-clear-draft-exit):
|
|
21
|
+
// with a draft x2 clears the input and x3 exits; an empty input
|
|
22
|
+
// keeps the previous x2 exit.
|
|
23
|
+
['Ctrl+C', `Draft: ${times}2 clear input, ${times}3 exit. Empty: ${times}2 exit`],
|
|
21
24
|
['Ctrl+L', 'Redraw the frame'],
|
|
22
25
|
['Ctrl+T', 'Show/hide thinking (off by default)'],
|
|
23
26
|
['Ctrl+I', 'Build the project index (bundled skill; asks first; needs keyboard enhancement)'],
|
|
@@ -128,6 +128,16 @@ function usePickerInput(props, state) {
|
|
|
128
128
|
activate();
|
|
129
129
|
else if (input === '/' && searchable)
|
|
130
130
|
setSearching(true);
|
|
131
|
+
// `e` edits the focused profile directly (profile-picker-edit-key). The
|
|
132
|
+
// branch is deliberately NOT guarded by `!props.manager`: the manager page
|
|
133
|
+
// gains the same one-keystroke path (there `selected === focused`, see the
|
|
134
|
+
// effect in usePickerState), which spares the Enter-to-the-action-row hop.
|
|
135
|
+
// No-op on the `Current custom` row and the action rows (`focused` is
|
|
136
|
+
// undefined there), and the `searching` early-return above keeps `e` a
|
|
137
|
+
// query character while typing a search.
|
|
138
|
+
else if (!key.ctrl && !key.meta && input.toLowerCase() === 'e' && focused) {
|
|
139
|
+
props.onEdit(focused);
|
|
140
|
+
}
|
|
131
141
|
});
|
|
132
142
|
}
|
|
133
143
|
function profilePickerLabel(props, entry) {
|
|
@@ -165,5 +175,6 @@ export function ModelProfilePicker(props) {
|
|
|
165
175
|
usePickerInput(props, state);
|
|
166
176
|
return _jsx(OverlayFrame, { ...props, title: props.error ?? (props.manager ? 'Manage profiles'
|
|
167
177
|
: `${props.role === 'main' ? 'Main' : 'Fast'} profile`), rows: buildPickerRows(props, state), scrollOffset: state.searching ? 0 : state.offset, hint: state.searching ? 'Type search | Enter done | Esc back'
|
|
168
|
-
: 'Enter
|
|
178
|
+
: props.manager ? 'Enter action | e edit | Esc back'
|
|
179
|
+
: 'Enter select | e edit | Esc back' });
|
|
169
180
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Facts, never translated toast text, determine action ownership and feedback. */
|
|
2
2
|
export interface StatusFeedback {
|
|
3
|
-
kind: 'confirm' | 'stopping' | 'force-stop' | 'exit' | 'copying' | 'copied' | 'copy-sent' | 'copy-cleanup' | 'copy-error' | 'notice';
|
|
3
|
+
kind: 'confirm' | 'stopping' | 'force-stop' | 'exit' | 'clear-input' | 'copying' | 'copied' | 'copy-sent' | 'copy-cleanup' | 'copy-error' | 'notice';
|
|
4
4
|
text: string;
|
|
5
5
|
level: 'info' | 'warn' | 'error';
|
|
6
6
|
}
|
|
@@ -19,6 +19,8 @@ export interface StatusFeedbackInput {
|
|
|
19
19
|
text: string;
|
|
20
20
|
};
|
|
21
21
|
ctrlCArmed?: boolean;
|
|
22
|
+
/** Draft present: the armed feedback names the clear rung, not exit. */
|
|
23
|
+
hasDraft?: boolean;
|
|
22
24
|
selectionPending?: boolean;
|
|
23
25
|
liveServices?: number;
|
|
24
26
|
toast?: {
|
|
@@ -7,7 +7,9 @@ export function projectStatusFeedback(input) {
|
|
|
7
7
|
let feedback;
|
|
8
8
|
if (input.ctrlCArmed && !input.copyState?.busy && !input.copyState?.cleanupPending
|
|
9
9
|
&& !input.selectionPending && !input.liveServices) {
|
|
10
|
-
feedback =
|
|
10
|
+
feedback = input.hasDraft
|
|
11
|
+
? { kind: 'clear-input', text: 'Press Ctrl+C again to clear input', level: 'warn' }
|
|
12
|
+
: { kind: 'exit', text: 'Press Ctrl+C again to exit', level: 'warn' };
|
|
11
13
|
}
|
|
12
14
|
else if (input.copyState?.cleanupPending) {
|
|
13
15
|
feedback = { kind: 'copy-cleanup', text: 'Copy failed; cleaning up', level: 'error' };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aragon-agent/cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.21",
|
|
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.33",
|
|
60
60
|
"chalk": "^5.6.2",
|
|
61
61
|
"cli-highlight": "^2.0.0",
|
|
62
62
|
"commander": "^12.1.0",
|