@aragon-agent/cli 0.6.20 → 0.6.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -12
- package/dist/agent/controller.js +13 -0
- package/dist/commands/builtins.js +2 -1
- package/dist/config/cli-commands.d.ts +1 -1
- package/dist/config/cli-commands.js +3 -0
- package/dist/config/schema.d.ts +28 -12
- package/dist/config/schema.js +4 -0
- package/dist/team/limits.d.ts +73 -13
- package/dist/team/limits.js +82 -15
- package/dist/team/normalize.js +7 -2
- package/dist/team/overseer.d.ts +154 -16
- package/dist/team/overseer.js +342 -46
- package/dist/team/report.d.ts +9 -0
- package/dist/team/report.js +32 -4
- package/dist/team/runtime.d.ts +51 -9
- package/dist/team/runtime.js +478 -71
- package/dist/team/task-tool.d.ts +7 -0
- package/dist/team/task-tool.js +2 -0
- package/dist/team/types.d.ts +43 -2
- package/dist/ui/App.js +80 -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/TeamPanel.d.ts +22 -0
- package/dist/ui/TeamPanel.js +71 -2
- 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/layout/team-panel.js +4 -1
- 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/team/task-tool.d.ts
CHANGED
|
@@ -39,6 +39,13 @@ export interface TaskToolDeps {
|
|
|
39
39
|
activeProvider: () => string;
|
|
40
40
|
/** The lead's cost table, so the report header states real spend (R-5). */
|
|
41
41
|
modelCost: () => ModelCost | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* The FAST tier's cost table, so the report's supervisor spend line states
|
|
44
|
+
* real spend (subagent-overseer-v2 D-7 / R-P1-2). OPTIONAL and returning
|
|
45
|
+
* `undefined`: an unpriced fast model renders `pricing unknown`, never a
|
|
46
|
+
* fabricated `$0.00` - the C-11 / RV-4 rule.
|
|
47
|
+
*/
|
|
48
|
+
fastModelCost?: () => ModelCost | undefined;
|
|
42
49
|
/**
|
|
43
50
|
* Run the dispatch with the LEAD's idle watchdog paused, `try/finally` inside.
|
|
44
51
|
*
|
package/dist/team/task-tool.js
CHANGED
|
@@ -123,8 +123,10 @@ export function createTaskTool(deps) {
|
|
|
123
123
|
// shape, and D-14 one level up). Every child ran the lead's own model,
|
|
124
124
|
// so the lead's cost table is the one honest price for the total.
|
|
125
125
|
const cost = deps.modelCost();
|
|
126
|
+
const fastCost = deps.fastModelCost?.();
|
|
126
127
|
return textResult(buildDispatchReport(outcome, {
|
|
127
128
|
...(cost ? { cost } : {}),
|
|
129
|
+
...(fastCost ? { fastCost } : {}),
|
|
128
130
|
}));
|
|
129
131
|
},
|
|
130
132
|
};
|
package/dist/team/types.d.ts
CHANGED
|
@@ -159,6 +159,22 @@ export interface SubagentRun {
|
|
|
159
159
|
* report reads "this row is the Nth body of the same brief".
|
|
160
160
|
*/
|
|
161
161
|
replacements?: number;
|
|
162
|
+
/**
|
|
163
|
+
* The most recent supervisor decision applied to this child
|
|
164
|
+
* (subagent-overseer-v2 section 6). TRANSIENT, never persisted - the
|
|
165
|
+
* same rule as `activity` / `activityArgs`; a resumed session has no
|
|
166
|
+
* live dispatch.
|
|
167
|
+
*
|
|
168
|
+
* `at` is ABSOLUTE epoch ms, the same units as `startedAt` / `endedAt`
|
|
169
|
+
* (R-P2-3), so the panel renders `nudged 2m` with one subtraction and
|
|
170
|
+
* no second clock convention. `reasonHead` <= 60 chars, for the UI
|
|
171
|
+
* badge line only.
|
|
172
|
+
*/
|
|
173
|
+
lastIntervention?: {
|
|
174
|
+
action: OverseerAction;
|
|
175
|
+
at: number;
|
|
176
|
+
reasonHead: string;
|
|
177
|
+
};
|
|
162
178
|
}
|
|
163
179
|
/** What the supervisor may do with one inspected child. */
|
|
164
180
|
export type OverseerAction = 'wait' | 'nudge' | 'replace' | 'abandon';
|
|
@@ -184,8 +200,14 @@ export interface OverseerIntervention {
|
|
|
184
200
|
at: number;
|
|
185
201
|
action: OverseerAction;
|
|
186
202
|
reason: string;
|
|
187
|
-
/**
|
|
188
|
-
|
|
203
|
+
/**
|
|
204
|
+
* What the trigger was: event stall, wall-clock check, post-mortem, or
|
|
205
|
+
* the supervisor going QUIET (subagent-overseer-v2 D-10) - the look
|
|
206
|
+
* budget ran out and supervision stopped waking this child. A VALUE OF
|
|
207
|
+
* THE UNION, never a reason prefix: a human-readable sentence is not a
|
|
208
|
+
* machine contract (the `run.error` rule).
|
|
209
|
+
*/
|
|
210
|
+
trigger: 'silence' | 'clock' | 'postmortem' | 'quiet';
|
|
189
211
|
}
|
|
190
212
|
/**
|
|
191
213
|
* A terminal supervisor decision handed to the WORKER LOOP, which is the only
|
|
@@ -231,6 +253,25 @@ export interface DispatchOutcome {
|
|
|
231
253
|
* total priced at the lead's table is the honest accounting.
|
|
232
254
|
*/
|
|
233
255
|
usage: TokenUsage;
|
|
256
|
+
/**
|
|
257
|
+
* The supervisor's own fast-tier spend for this dispatch
|
|
258
|
+
* (subagent-overseer-v2 D-7). Absent when nothing supervised the
|
|
259
|
+
* dispatch; read straight off the provider at dispatch end so there is
|
|
260
|
+
* exactly one accounting of it.
|
|
261
|
+
*/
|
|
262
|
+
overseerUsage?: TokenUsage;
|
|
263
|
+
/** Inspection calls made, including ones that failed softly. */
|
|
264
|
+
overseerCalls?: number;
|
|
265
|
+
/**
|
|
266
|
+
* EDGE-LATCHED, once per dispatch (D-10): the fast tier was missing at
|
|
267
|
+
* least one cadence tick and the children waited UNASSISTED - announced,
|
|
268
|
+
* never killed. The per-child wait events of a degraded tick
|
|
269
|
+
* deliberately do NOT enter `interventions`; this flag plus the tick
|
|
270
|
+
* count below are the report's aggregate note.
|
|
271
|
+
*/
|
|
272
|
+
overseerDegraded?: boolean;
|
|
273
|
+
/** How many cadence ticks ran unassisted (the aggregate note's number). */
|
|
274
|
+
overseerDegradedTicks?: number;
|
|
234
275
|
}
|
|
235
276
|
/**
|
|
236
277
|
* What the UI renders: a cheap immutable projection, rebuilt per event.
|
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 });
|
|
@@ -657,6 +664,20 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
657
664
|
const snap = controller.getTeamSnapshot();
|
|
658
665
|
if (snap)
|
|
659
666
|
dispatch({ type: 'teamUpdate', snapshot: snap });
|
|
667
|
+
// G-4 / AC-5 (subagent-overseer-v2): a TERMINAL verdict is the
|
|
668
|
+
// one decision the user must not discover from the final report
|
|
669
|
+
// - it removes work they watched running. wait and nudge stay
|
|
670
|
+
// panel-only so the transcript gains no per-tick noise.
|
|
671
|
+
if (event.decision.action === 'replace' || event.decision.action === 'abandon') {
|
|
672
|
+
const verb = event.decision.action === 'replace' ? 'replaced' : 'abandoned';
|
|
673
|
+
const level = event.decision.action === 'replace' ? 'info' : 'warn';
|
|
674
|
+
const reason = event.decision.reason.slice(0, 120);
|
|
675
|
+
dispatch({
|
|
676
|
+
type: 'notice',
|
|
677
|
+
level,
|
|
678
|
+
text: `team supervisor ${verb} ${event.label} - ${reason}`,
|
|
679
|
+
});
|
|
680
|
+
}
|
|
660
681
|
break;
|
|
661
682
|
}
|
|
662
683
|
case 'usage': {
|
|
@@ -1145,6 +1166,21 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1145
1166
|
const onDraftRows = useCallback((next) => {
|
|
1146
1167
|
setDraftRows(previous => previous === next ? previous : next);
|
|
1147
1168
|
}, []);
|
|
1169
|
+
/**
|
|
1170
|
+
* Draft presence, written to a REF on purpose (ctrl-c-clear-draft-exit):
|
|
1171
|
+
* promoting it to state would re-render the transcript on the first
|
|
1172
|
+
* keystroke of every message - the exact cost Composer.tsx documents
|
|
1173
|
+
* for keeping `hasDraft` local. The ladder reads the ref inside its key
|
|
1174
|
+
* handler, where freshness is guaranteed; the render-time feedback
|
|
1175
|
+
* uses `hasDraftRef.current && !draftClearedByCtrlC` instead.
|
|
1176
|
+
*/
|
|
1177
|
+
const hasDraftRef = useRef(false);
|
|
1178
|
+
const onDraftPresence = useCallback((hasDraft) => {
|
|
1179
|
+
hasDraftRef.current = hasDraft;
|
|
1180
|
+
// A draft that re-appears after a ladder clear re-arms the clear rung.
|
|
1181
|
+
if (hasDraft)
|
|
1182
|
+
setDraftClearedByCtrlC(false);
|
|
1183
|
+
}, []);
|
|
1148
1184
|
// --- Auto-update bridge (cli-auto-update section 3.8 / 6.3). ------------
|
|
1149
1185
|
//
|
|
1150
1186
|
// TWO ARRIVAL ORDERS, AND BOTH HAPPEN. The service is built by a dynamic
|
|
@@ -1743,7 +1779,43 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1743
1779
|
toast('warn', `Stopping ${live} service${live === 1 ? '' : 's'}.`);
|
|
1744
1780
|
return;
|
|
1745
1781
|
}
|
|
1782
|
+
//
|
|
1783
|
+
// RENDER-TIMING INVARIANT (part of this fix, not an implementation
|
|
1784
|
+
// detail): the status feedback is evaluated during render, but
|
|
1785
|
+
// `hasDraftRef` is a ref that does not itself trigger one. Every
|
|
1786
|
+
// rung still lands exactly one render - arm bumps `metricsClock`,
|
|
1787
|
+
// the clear rung bumps `draftClearNonce`/`draftClearedByCtrlC` - and
|
|
1788
|
+
// the async clear (nonce -> effect -> 'clear' -> the onDraftChange
|
|
1789
|
+
// transition) refills the ref within the same tick, far faster than
|
|
1790
|
+
// a human's next press. `draftClearedByCtrlC` exists because the
|
|
1791
|
+
// nonce render happens BEFORE that chain: without it the feedback
|
|
1792
|
+
// would still read hasDraft=true and repeat the clear prompt after
|
|
1793
|
+
// clearing. If a future rung ever becomes a pure ref write with no
|
|
1794
|
+
// state change, its feedback text will silently go stale.
|
|
1795
|
+
//
|
|
1796
|
+
// TWO ACCEPTED EDGES (facts drive the ladder, by design):
|
|
1797
|
+
// - arm (empty, "to exit") then PASTE a draft within 1.5s: the
|
|
1798
|
+
// second press clears instead of exiting - the cleared text is
|
|
1799
|
+
// still in the clipboard, so nothing is actually lost.
|
|
1800
|
+
// - arm (draft, "to clear") then Ctrl+U kills the draft by hand:
|
|
1801
|
+
// the second press falls to the exit branch - with nothing left
|
|
1802
|
+
// to clear, the next rung owns the press (tmux/htop behave the
|
|
1803
|
+
// same way).
|
|
1804
|
+
//
|
|
1805
|
+
// RUNG TWO/TREE, composed from two existing facts: armed x
|
|
1806
|
+
// hasDraft. No new counter, no new timer - clearing the draft
|
|
1807
|
+
// flips hasDraftRef to false, so the NEXT press naturally lands on
|
|
1808
|
+
// the exit branch: draft present -> x2 clears, x3 exits; empty ->
|
|
1809
|
+
// x2 exits, byte-identical to the previous behaviour.
|
|
1746
1810
|
if (ctrlCArmed.current) {
|
|
1811
|
+
if (hasDraftRef.current) {
|
|
1812
|
+
setDraftClearNonce(value => value + 1);
|
|
1813
|
+
setDraftClearedByCtrlC(true);
|
|
1814
|
+
// Stays armed and the 1500ms clock keeps running: the clear
|
|
1815
|
+
// rung must not re-arm the whole ladder (that would make
|
|
1816
|
+
// exiting a draft x4 presses).
|
|
1817
|
+
return;
|
|
1818
|
+
}
|
|
1747
1819
|
doExit();
|
|
1748
1820
|
return;
|
|
1749
1821
|
}
|
|
@@ -1755,6 +1827,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1755
1827
|
ctrlCTimer.current = setTimeout(() => {
|
|
1756
1828
|
ctrlCTimer.current = null;
|
|
1757
1829
|
ctrlCArmed.current = false;
|
|
1830
|
+
setDraftClearedByCtrlC(false);
|
|
1758
1831
|
setMetricsClock(value => value + 1);
|
|
1759
1832
|
}, 1500);
|
|
1760
1833
|
return;
|
|
@@ -2135,6 +2208,10 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2135
2208
|
const feedbackProjection = projectStatusFeedback({
|
|
2136
2209
|
interactionPhase: interactionPhase.current, interruptPhase: interruptGesture.current.phase,
|
|
2137
2210
|
completion, overlay, copyState, copyResult, ctrlCArmed: ctrlCArmed.current,
|
|
2211
|
+
// The flag, not the raw ref: the nonce render precedes the async
|
|
2212
|
+
// clear, so the ref alone would repeat the clear wording (see the
|
|
2213
|
+
// render-timing invariant beside the ladder).
|
|
2214
|
+
hasDraft: hasDraftRef.current && !draftClearedByCtrlC,
|
|
2138
2215
|
selectionPending: selectionController?.hasPendingSelection() ?? false, liveServices,
|
|
2139
2216
|
toast: latestToast ? { text: latestToast.text,
|
|
2140
2217
|
level: latestToast.level === 'success' ? 'info' : latestToast.level } : undefined,
|
|
@@ -2159,10 +2236,11 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2159
2236
|
hints: { interactionPhase: interactionPhase.current, cols, completion, overlay,
|
|
2160
2237
|
selectionPending: selectionController?.hasPendingSelection() ?? false,
|
|
2161
2238
|
copyInFlight: copyState.busy, copyCleanupPending: copyState.cleanupPending,
|
|
2162
|
-
|
|
2239
|
+
hasDraft: hasDraftRef.current, services: liveServices,
|
|
2240
|
+
hintsEnabled: cfg.hints, modeToggleKey,
|
|
2163
2241
|
interruptPhase: interruptGesture.current.phase,
|
|
2164
2242
|
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' }) }));
|
|
2243
|
+
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
2244
|
const team = state.team ? (_jsx(TeamPanel, { snapshot: state.team, layout: teamLayout, rows: rows, cols: contentCols, reducedMotion: viewReducedMotion, theme: theme, caps: caps })) : null;
|
|
2167
2245
|
const rail = showRail && state.todos ? (_jsx(TodoPanel, { snapshot: state.todos, width: railWidth, rows: railRows, running: running, reducedMotion: viewReducedMotion, theme: theme, caps: caps })) : null;
|
|
2168
2246
|
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
|
package/dist/ui/TeamPanel.d.ts
CHANGED
|
@@ -57,4 +57,26 @@ export declare function activityBudget(cols: number): number;
|
|
|
57
57
|
* columns and break at 80.
|
|
58
58
|
*/
|
|
59
59
|
export declare function activityLine(run: SubagentRun, cols: number): string;
|
|
60
|
+
/**
|
|
61
|
+
* The row's supervisor badge (subagent-overseer-v2 AC-5): what the supervisor
|
|
62
|
+
* last did to THIS child and how long ago, e.g. `nudged 2m03s`.
|
|
63
|
+
*
|
|
64
|
+
* `lastIntervention.at` is ABSOLUTE epoch ms (R-P2-3), the same units as
|
|
65
|
+
* `startedAt`, so this is one subtraction - no second clock convention.
|
|
66
|
+
*/
|
|
67
|
+
export declare function overseerBadge(run: SubagentRun, at: number): string;
|
|
68
|
+
/**
|
|
69
|
+
* The activity column WITH the badge composed in (AC-5 / risk table): the
|
|
70
|
+
* badge rides the SAME `activityBudget(cols)` allocation the prose uses, so
|
|
71
|
+
* a row can never grow past its terminal; on a narrow terminal the badge
|
|
72
|
+
* degrades to the action word alone - the who-acted fact outranks the
|
|
73
|
+
* what-they-are-doing tail exactly when there is room for only one.
|
|
74
|
+
*/
|
|
75
|
+
export declare function activityWithBadge(run: SubagentRun, cols: number, at: number): string;
|
|
76
|
+
/**
|
|
77
|
+
* The panel's one supervisor status line (AC-5): the most recent intervention
|
|
78
|
+
* across the roster, human-phrased. Empty when the supervisor has not acted
|
|
79
|
+
* yet - furniture that says nothing is worse than no line.
|
|
80
|
+
*/
|
|
81
|
+
export declare function supervisorStatusLine(runs: SubagentRun[], at: number): string;
|
|
60
82
|
export declare function TeamPanel({ snapshot, rows, cols, reducedMotion, theme, caps, now, layout, }: TeamPanelProps): React.ReactElement | null;
|
package/dist/ui/TeamPanel.js
CHANGED
|
@@ -101,6 +101,69 @@ export function activityLine(run, cols) {
|
|
|
101
101
|
return 'aborted';
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
|
+
/** The past-tense word the supervisor badge and status line render. */
|
|
105
|
+
function interventionWord(action) {
|
|
106
|
+
switch (action) {
|
|
107
|
+
case 'nudge':
|
|
108
|
+
return 'nudged';
|
|
109
|
+
case 'replace':
|
|
110
|
+
return 'replaced';
|
|
111
|
+
case 'abandon':
|
|
112
|
+
return 'abandoned';
|
|
113
|
+
default:
|
|
114
|
+
return 'waited';
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* The row's supervisor badge (subagent-overseer-v2 AC-5): what the supervisor
|
|
119
|
+
* last did to THIS child and how long ago, e.g. `nudged 2m03s`.
|
|
120
|
+
*
|
|
121
|
+
* `lastIntervention.at` is ABSOLUTE epoch ms (R-P2-3), the same units as
|
|
122
|
+
* `startedAt`, so this is one subtraction - no second clock convention.
|
|
123
|
+
*/
|
|
124
|
+
export function overseerBadge(run, at) {
|
|
125
|
+
const last = run.lastIntervention;
|
|
126
|
+
if (last === undefined)
|
|
127
|
+
return '';
|
|
128
|
+
return `${interventionWord(last.action)} ${formatDuration(Math.max(0, at - last.at))}`;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* The activity column WITH the badge composed in (AC-5 / risk table): the
|
|
132
|
+
* badge rides the SAME `activityBudget(cols)` allocation the prose uses, so
|
|
133
|
+
* a row can never grow past its terminal; on a narrow terminal the badge
|
|
134
|
+
* degrades to the action word alone - the who-acted fact outranks the
|
|
135
|
+
* what-they-are-doing tail exactly when there is room for only one.
|
|
136
|
+
*/
|
|
137
|
+
export function activityWithBadge(run, cols, at) {
|
|
138
|
+
const last = run.lastIntervention;
|
|
139
|
+
if (last === undefined)
|
|
140
|
+
return activityLine(run, cols);
|
|
141
|
+
const badge = overseerBadge(run, at);
|
|
142
|
+
if (cols < TEAM_LIMITS.activityWideCols)
|
|
143
|
+
return interventionWord(last.action);
|
|
144
|
+
return sanitizeActivity(`${badge}; ${activityLine(run, cols)}`, activityBudget(cols));
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* The panel's one supervisor status line (AC-5): the most recent intervention
|
|
148
|
+
* across the roster, human-phrased. Empty when the supervisor has not acted
|
|
149
|
+
* yet - furniture that says nothing is worse than no line.
|
|
150
|
+
*/
|
|
151
|
+
export function supervisorStatusLine(runs, at) {
|
|
152
|
+
let label = '';
|
|
153
|
+
let latest;
|
|
154
|
+
for (const run of runs) {
|
|
155
|
+
const last = run.lastIntervention;
|
|
156
|
+
if (last !== undefined && (latest === undefined || last.at > latest.at)) {
|
|
157
|
+
latest = last;
|
|
158
|
+
label = run.label;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (latest === undefined)
|
|
162
|
+
return '';
|
|
163
|
+
const ago = formatDuration(Math.max(0, at - latest.at));
|
|
164
|
+
const head = latest.reasonHead.length > 0 ? ` (${latest.reasonHead})` : '';
|
|
165
|
+
return `supervisor: ${interventionWord(latest.action)} ${label} ${ago} ago${head}`;
|
|
166
|
+
}
|
|
104
167
|
export function TeamPanel({ snapshot, rows, cols, reducedMotion, theme, caps, now, layout, }) {
|
|
105
168
|
const projection = layout ?? buildTeamPanelLayout({
|
|
106
169
|
snapshot, terminalRows: rows, availableRows: Infinity,
|
|
@@ -130,8 +193,14 @@ export function TeamPanel({ snapshot, rows, cols, reducedMotion, theme, caps, no
|
|
|
130
193
|
// `reducedMotion` and a non-Unicode terminal fall back to the same
|
|
131
194
|
// static marker, exactly as `AssistantEntry` and `ToolCard` already do.
|
|
132
195
|
const animate = isRunning(run) && !reducedMotion && caps.unicode;
|
|
133
|
-
return (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: color, children: [' ', animate ? _jsx(Spinner, { type: "dots" }) : marker(run, glyphs), ' '] }), _jsx(Text, { color: theme.primary, children: run.label.padEnd(8).slice(0, 8) }), _jsx(Box, { flexGrow: 1, flexShrink: 1, overflow: "hidden", children: _jsx(Text, { wrap: "truncate", color: theme.muted, children: run.description.replace(/\s+/g, ' ') }) }), _jsx(Box, { flexShrink: 1, overflow: "hidden", children: _jsxs(Text, { wrap: "truncate", color: color, children: [' ',
|
|
134
|
-
}), hiddenTotal > 0 && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', "+", hiddenTotal, " more", hiddenRunning > 0 ? ` (${hiddenRunning} running)` : ''] })),
|
|
196
|
+
return (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: color, children: [' ', animate ? _jsx(Spinner, { type: "dots" }) : marker(run, glyphs), ' '] }), _jsx(Text, { color: theme.primary, children: run.label.padEnd(8).slice(0, 8) }), _jsx(Box, { flexGrow: 1, flexShrink: 1, overflow: "hidden", children: _jsx(Text, { wrap: "truncate", color: theme.muted, children: run.description.replace(/\s+/g, ' ') }) }), _jsx(Box, { flexShrink: 1, overflow: "hidden", children: _jsxs(Text, { wrap: "truncate", color: color, children: [' ', activityWithBadge(run, cols, at)] }) }), _jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', run.startedAt ? formatDuration(Math.max(0, (run.endedAt ?? at) - run.startedAt)) : ''] })] }, run.label));
|
|
197
|
+
}), hiddenTotal > 0 && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', "+", hiddenTotal, " more", hiddenRunning > 0 ? ` (${hiddenRunning} running)` : ''] })), (() => {
|
|
198
|
+
// The panel's supervisor line (AC-5): one row, only when the
|
|
199
|
+
// supervisor has actually acted - the per-row badges above already
|
|
200
|
+
// carry the per-child facts.
|
|
201
|
+
const line = supervisorStatusLine(snapshot.runs, at);
|
|
202
|
+
return line ? (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', line] })) : null;
|
|
203
|
+
})(), snapshot.lastMessage && (_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [' ', glyphs.teamMail, " ", snapshot.lastMessage.from, " ", glyphs.arrowRight, " ", snapshot.lastMessage.to, ": ", snapshot.lastMessage.subject.replace(/\s+/g, ' ')] }))] }));
|
|
135
204
|
}
|
|
136
205
|
function marker(run, glyphs) {
|
|
137
206
|
if (run.phase === 'done')
|
|
@@ -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'],
|
|
@@ -12,7 +12,10 @@ export function buildTeamPanelLayout(input) {
|
|
|
12
12
|
}
|
|
13
13
|
const selection = selectPanelRows(input.snapshot.runs, TEAM_LIMITS.panelMaxRows);
|
|
14
14
|
const natural = 1 + selection.visible.length + Number(selection.hiddenTotal > 0)
|
|
15
|
-
+ Number(Boolean(input.snapshot.lastMessage))
|
|
15
|
+
+ Number(Boolean(input.snapshot.lastMessage))
|
|
16
|
+
// The supervisor status line (subagent-overseer-v2 AC-5) occupies one
|
|
17
|
+
// row whenever any run carries an intervention badge.
|
|
18
|
+
+ Number(input.snapshot.runs.some((run) => run.lastIntervention !== undefined));
|
|
16
19
|
const collapsed = nonNegativeInt(input.terminalRows) < TEAM_LIMITS.panelCollapseRows
|
|
17
20
|
|| available - natural < TODO_LIMITS.panelMinRows;
|
|
18
21
|
return { ...selection, collapsed, rowCount: collapsed ? 1 : natural };
|
|
@@ -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' };
|