@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.
- package/CHANGELOG.md +10 -1
- package/README.md +97 -20
- package/dist/agent/controller.d.ts +38 -3
- package/dist/agent/controller.js +151 -60
- package/dist/agent/model-profile-settings.d.ts +31 -0
- package/dist/agent/model-profile-settings.js +199 -0
- package/dist/agent/queued-messages.d.ts +20 -0
- package/dist/agent/queued-messages.js +46 -0
- package/dist/agent/reducer.d.ts +21 -2
- package/dist/agent/reducer.js +65 -37
- package/dist/cli.js +15 -1
- package/dist/commands/builtins.js +83 -42
- package/dist/commands/queue-command.d.ts +3 -0
- package/dist/commands/queue-command.js +5 -0
- package/dist/commands/registry.d.ts +3 -0
- package/dist/compaction/child.d.ts +4 -2
- package/dist/compaction/child.js +4 -1
- package/dist/compaction/compactor.d.ts +7 -2
- package/dist/compaction/compactor.js +13 -11
- package/dist/compaction/context-command.js +5 -0
- package/dist/compaction/wiring.d.ts +4 -3
- package/dist/compaction/wiring.js +6 -1
- package/dist/config/cli-commands.d.ts +2 -0
- package/dist/config/cli-commands.js +16 -4
- package/dist/config/load.d.ts +2 -1
- package/dist/config/load.js +15 -8
- package/dist/config/model-profile-resolution.d.ts +20 -0
- package/dist/config/model-profile-resolution.js +81 -0
- package/dist/config/model-profile-store.d.ts +67 -0
- package/dist/config/model-profile-store.js +153 -0
- package/dist/config/model-profiles.d.ts +44 -0
- package/dist/config/model-profiles.js +121 -0
- package/dist/config/schema.d.ts +9 -0
- package/dist/config/store.d.ts +11 -1
- package/dist/config/store.js +43 -27
- package/dist/fast/resolve.d.ts +2 -1
- package/dist/fast/resolve.js +6 -4
- package/dist/fast/wiring.d.ts +5 -3
- package/dist/fast/wiring.js +8 -4
- package/dist/input/enter-sequences.d.ts +11 -10
- package/dist/input/enter-sequences.js +29 -27
- package/dist/input/stdin-filter.js +93 -28
- package/dist/logging/install.d.ts +3 -0
- package/dist/logging/install.js +4 -2
- package/dist/logging/secret-registry.d.ts +2 -0
- package/dist/logging/secret-registry.js +18 -0
- package/dist/session/persist.d.ts +6 -7
- package/dist/session/persist.js +6 -9
- package/dist/session/store.d.ts +4 -1
- package/dist/session/validate-session.d.ts +3 -0
- package/dist/session/validate-session.js +207 -0
- package/dist/team/runtime.d.ts +2 -1
- package/dist/team/subagent.d.ts +3 -1
- package/dist/team/subagent.js +3 -1
- package/dist/ui/ActivityLine.d.ts +6 -70
- package/dist/ui/ActivityLine.js +12 -34
- package/dist/ui/App.d.ts +1 -0
- package/dist/ui/App.js +161 -226
- package/dist/ui/BottomStatusRow.d.ts +6 -86
- package/dist/ui/BottomStatusRow.js +11 -26
- package/dist/ui/Composer.d.ts +18 -84
- package/dist/ui/Composer.js +9 -100
- package/dist/ui/PromptInput.d.ts +6 -2
- package/dist/ui/PromptInput.js +91 -105
- package/dist/ui/QueuePanel.d.ts +8 -0
- package/dist/ui/QueuePanel.js +9 -0
- package/dist/ui/QueueStatusRow.d.ts +24 -0
- package/dist/ui/QueueStatusRow.js +51 -0
- package/dist/ui/StatusBar.d.ts +15 -149
- package/dist/ui/StatusBar.js +27 -112
- package/dist/ui/clipboard-task.d.ts +29 -0
- package/dist/ui/clipboard-task.js +66 -0
- package/dist/ui/clipboard.d.ts +29 -50
- package/dist/ui/clipboard.js +141 -75
- package/dist/ui/composer-input.d.ts +28 -0
- package/dist/ui/composer-input.js +46 -0
- package/dist/ui/composer-limits.d.ts +3 -4
- package/dist/ui/composer-limits.js +4 -9
- package/dist/ui/composer-rows.d.ts +1 -1
- package/dist/ui/composer-rows.js +24 -137
- package/dist/ui/editor-navigation.d.ts +45 -0
- package/dist/ui/editor-navigation.js +85 -0
- package/dist/ui/editor-reducer.d.ts +9 -0
- package/dist/ui/editor-reducer.js +31 -15
- package/dist/ui/enter-frames.d.ts +15 -31
- package/dist/ui/enter-frames.js +14 -33
- package/dist/ui/interaction-copy.d.ts +77 -0
- package/dist/ui/interaction-copy.js +40 -0
- package/dist/ui/interaction-hints.d.ts +18 -0
- package/dist/ui/interaction-hints.js +92 -0
- package/dist/ui/layout/budget.js +1 -1
- package/dist/ui/layout/queue-layout.d.ts +19 -0
- package/dist/ui/layout/queue-layout.js +26 -0
- package/dist/ui/layout/status-layout.d.ts +55 -0
- package/dist/ui/layout/status-layout.js +109 -0
- package/dist/ui/layout/todo-layout.d.ts +1 -0
- package/dist/ui/layout/todo-layout.js +1 -1
- package/dist/ui/overlays/HelpOverlay.js +11 -3
- package/dist/ui/overlays/ModelProfileEditor.d.ts +12 -0
- package/dist/ui/overlays/ModelProfileEditor.js +103 -0
- package/dist/ui/overlays/ModelProfilePicker.d.ts +35 -0
- package/dist/ui/overlays/ModelProfilePicker.js +167 -0
- package/dist/ui/overlays/QueueOverlay.d.ts +22 -0
- package/dist/ui/overlays/QueueOverlay.js +79 -0
- package/dist/ui/overlays/SettingsScreen.d.ts +9 -2
- package/dist/ui/overlays/SettingsScreen.js +347 -3
- package/dist/ui/overlays/model-profile-state.d.ts +74 -0
- package/dist/ui/overlays/model-profile-state.js +119 -0
- package/dist/ui/run-status-row.d.ts +7 -19
- package/dist/ui/run-status-row.js +2 -18
- package/dist/ui/selection/selection-controller.d.ts +2 -2
- package/dist/ui/selection/selection-controller.js +58 -20
- package/dist/ui/transcript-text.js +20 -12
- package/dist/ui/use-wheel-routing.js +2 -1
- package/package.json +2 -2
package/dist/ui/App.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { modelSettingsResultMessage } from '../agent/model-profile-settings.js';
|
|
3
|
+
import { selectTranscriptEntries, mergePendingEntries } from '../agent/queued-messages.js';
|
|
2
4
|
/**
|
|
3
5
|
* App — the root Ink component. Owns the view reducer, subscribes to the
|
|
4
6
|
* controller's event stream (with a streaming coalescer), wires global
|
|
@@ -18,7 +20,8 @@ import { appendPrompt, loadPromptHistory } from '../config/prompt-history.js';
|
|
|
18
20
|
import { bumpSubmitCount, setMouseNoticeVersion, setVtInputNoticeVersion, } from '../config/ui-state.js';
|
|
19
21
|
import { getLogger } from '../logging/logger.js';
|
|
20
22
|
import { isTerminalStatus } from '../proc/types.js';
|
|
21
|
-
import {
|
|
23
|
+
import { getSecrets } from '../logging/secret-registry.js';
|
|
24
|
+
import { redactText } from '../logging/redact.js';
|
|
22
25
|
import { detectCapabilities } from './capabilities.js';
|
|
23
26
|
import { useStartupNotices, MOUSE_NOTICE_VERSION, VT_INPUT_NOTICE_VERSION, } from './use-startup-notices.js';
|
|
24
27
|
import { getTheme } from './theme.js';
|
|
@@ -32,7 +35,6 @@ import { StatusBar } from './StatusBar.js';
|
|
|
32
35
|
import { secondsLeft } from '../agent/retry-view.js';
|
|
33
36
|
import { TeamPanel } from './TeamPanel.js';
|
|
34
37
|
import { TodoPanel } from './TodoPanel.js';
|
|
35
|
-
import { ActivityLine, liveSpinner } from './ActivityLine.js';
|
|
36
38
|
import { BottomStatusRow } from './BottomStatusRow.js';
|
|
37
39
|
import { UpdateLine } from './UpdateLine.js';
|
|
38
40
|
// The updater's two PURE modules. `shouldRenderUpdateLine` decides the row's
|
|
@@ -60,7 +62,7 @@ import { publishExitSnapshot } from './exit-snapshot.js';
|
|
|
60
62
|
import { OverlayFrame } from './layout/OverlayFrame.js';
|
|
61
63
|
import { helpRows } from './overlays/HelpOverlay.js';
|
|
62
64
|
import { ModelPicker } from './overlays/ModelPicker.js';
|
|
63
|
-
import { SettingsScreen, compactionSettingsFrom, fastSettingsFrom,
|
|
65
|
+
import { SettingsScreen, compactionSettingsFrom, fastSettingsFrom, } from './overlays/SettingsScreen.js';
|
|
64
66
|
import { ConfirmDialog } from './overlays/ConfirmDialog.js';
|
|
65
67
|
import { QuestionOverlay } from './overlays/QuestionOverlay.js';
|
|
66
68
|
import { PlanReviewOverlay } from './overlays/PlanReviewOverlay.js';
|
|
@@ -68,7 +70,11 @@ import { CommandRegistry, runSlashInput } from '../commands/registry.js';
|
|
|
68
70
|
import { registerBuiltinCommands } from '../commands/builtins.js';
|
|
69
71
|
import { makeSkillsCommand, registerSkillCommands } from '../commands/skills.js';
|
|
70
72
|
import { MODE_LABEL, MODE_TOGGLE_KEYS, nextMode } from '../agent/agent-mode.js';
|
|
71
|
-
import {
|
|
73
|
+
import { createClipboardCoordinator } from './clipboard-task.js';
|
|
74
|
+
import { QueuePanel } from './QueuePanel.js';
|
|
75
|
+
import { QueueOverlay } from './overlays/QueueOverlay.js';
|
|
76
|
+
import { buildQueueLayout } from './layout/queue-layout.js';
|
|
77
|
+
import { interactionCopy } from './interaction-copy.js';
|
|
72
78
|
import { emptyTailState } from './layout/follow-state.js';
|
|
73
79
|
/**
|
|
74
80
|
* Decide what the `Max tokens` field means, WITHOUT letting a bad value in it
|
|
@@ -194,6 +200,8 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
194
200
|
// closing an overlay cannot leave a stale offset behind, and reset whenever
|
|
195
201
|
// the active overlay changes.
|
|
196
202
|
const [overlayScroll, setOverlayScroll] = useState(0);
|
|
203
|
+
const [completion, setCompletion] = useState('none');
|
|
204
|
+
const [copyState, setCopyState] = useState({ busy: false, cleanupPending: false });
|
|
197
205
|
// Bumped whenever the installed skill set changes. The command registry and
|
|
198
206
|
// the completion list are memoized against it, so a skill installed mid-session
|
|
199
207
|
// gets its `/<name>` command and its autocomplete entry immediately.
|
|
@@ -433,6 +441,11 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
433
441
|
// makes stale events inert WITHOUT making the next run's events inert too.
|
|
434
442
|
let generation = controller.runGeneration;
|
|
435
443
|
const unsubscribe = controller.subscribe((event) => {
|
|
444
|
+
// Receipts describe committed history even when force-stop invalidated the run.
|
|
445
|
+
if (event.type === 'steering_accepted') {
|
|
446
|
+
dispatch({ type: 'steeringAccepted', ids: event.ids });
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
436
449
|
if (controller.runGeneration !== generation) {
|
|
437
450
|
// The engine is unwinding a force-stopped run. Adopt the new generation
|
|
438
451
|
// only once it has finished doing so, so a late `agent_end` cannot be
|
|
@@ -937,7 +950,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
937
950
|
// --- Exit snapshot: the only way state reaches cli.tsx (§4.4). ----------
|
|
938
951
|
useEffect(() => {
|
|
939
952
|
publishExitSnapshot({
|
|
940
|
-
entries: state.entries,
|
|
953
|
+
entries: mergePendingEntries(state.entries, state.pendingSteering),
|
|
941
954
|
usageTotal: state.usageTotal,
|
|
942
955
|
provider: cfg.provider,
|
|
943
956
|
model: cfg.model,
|
|
@@ -947,7 +960,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
947
960
|
// count (K-6).
|
|
948
961
|
droppedEntries: state.droppedEntries,
|
|
949
962
|
});
|
|
950
|
-
}, [state.entries, state.usageTotal, state.droppedEntries, cfg.provider, cfg.model]);
|
|
963
|
+
}, [state.entries, state.pendingSteering, state.usageTotal, state.droppedEntries, cfg.provider, cfg.model]);
|
|
951
964
|
// --- `/perf` snapshot channel (tui-render-performance §5.4). ------------
|
|
952
965
|
//
|
|
953
966
|
// The deps are the two `useCallback`d members, NOT the handle objects: both
|
|
@@ -1161,22 +1174,36 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1161
1174
|
// redraw when the frame differ has no cache to repaint from. Both are assigned
|
|
1162
1175
|
// here and cleared on unmount, the same shape `updateBridge` uses.
|
|
1163
1176
|
const selectionBridge = terminal?.selection;
|
|
1177
|
+
const copyCoordinator = useMemo(() => createClipboardCoordinator({
|
|
1178
|
+
clipboard: { write: terminal?.writeForeign },
|
|
1179
|
+
onRequest: () => {
|
|
1180
|
+
ctrlCArmed.current = false;
|
|
1181
|
+
if (ctrlCTimer.current)
|
|
1182
|
+
clearTimeout(ctrlCTimer.current);
|
|
1183
|
+
ctrlCTimer.current = null;
|
|
1184
|
+
},
|
|
1185
|
+
onStateChange: setCopyState,
|
|
1186
|
+
onBusy: ({ cleanupPending }) => toast('info', cleanupPending
|
|
1187
|
+
? '\u590d\u5236\u5931\u8d25\uff0c\u6b63\u5728\u6e05\u7406' : '\u6b63\u5728\u590d\u5236'),
|
|
1188
|
+
onResult: (result, request) => {
|
|
1189
|
+
selectionBridge?.onCopied?.(result, request.lines, request.text.length);
|
|
1190
|
+
if (result.status === 'confirmed') {
|
|
1191
|
+
toast('success', `\u5df2\u590d\u5236 ${request.lines} \u884c`);
|
|
1192
|
+
}
|
|
1193
|
+
else if (result.status === 'sent') {
|
|
1194
|
+
toast('info', '\u5df2\u8bf7\u6c42\u7ec8\u7aef\u590d\u5236\uff0c\u7ed3\u679c\u672a\u786e\u8ba4');
|
|
1195
|
+
}
|
|
1196
|
+
else {
|
|
1197
|
+
const text = `${interactionCopy.copyFailed}\uff1a${interactionCopy.copyReasons[result.reason]}`;
|
|
1198
|
+
toast('warn', text);
|
|
1199
|
+
notify('error', text);
|
|
1200
|
+
}
|
|
1201
|
+
},
|
|
1202
|
+
}), []);
|
|
1203
|
+
useEffect(() => () => copyCoordinator.dispose(), [copyCoordinator]);
|
|
1164
1204
|
useEffect(() => {
|
|
1165
1205
|
if (!selectionBridge)
|
|
1166
1206
|
return undefined;
|
|
1167
|
-
selectionBridge.onCopied = (via, lines, chars) => {
|
|
1168
|
-
// IT NAMES THE MECHANISM RATHER THAN CLAIMING SUCCESS (R-7). Neither OSC 52
|
|
1169
|
-
// nor a spawned `xclip` is detectable: tmux without `set -g set-clipboard
|
|
1170
|
-
// on` swallows the first silently, and the second is best-effort by
|
|
1171
|
-
// construction. A toast that said "copied successfully" would be guessing.
|
|
1172
|
-
const noun = lines === 1 ? 'line' : 'lines';
|
|
1173
|
-
if (via === 'none') {
|
|
1174
|
-
toast('warn', 'Nothing could be copied - no clipboard mechanism is available.');
|
|
1175
|
-
return;
|
|
1176
|
-
}
|
|
1177
|
-
const how = via === 'osc52' ? 'terminal clipboard' : 'system clipboard';
|
|
1178
|
-
toast('success', `Sent ${lines} ${noun} (${chars} chars) to the ${how}.`);
|
|
1179
|
-
};
|
|
1180
1207
|
// I-5 of `frame-differ.ts`: writing escapes here would be a foreign write.
|
|
1181
1208
|
// The nonce is the carrier Ctrl+L already uses (§4.13).
|
|
1182
1209
|
selectionBridge.requestRedraw = () => setRedrawNonce((n) => n + 1);
|
|
@@ -1283,6 +1310,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1283
1310
|
};
|
|
1284
1311
|
const makeCtx = (args) => ({
|
|
1285
1312
|
args,
|
|
1313
|
+
requestCopy: copyCoordinator.requestCopy,
|
|
1286
1314
|
controller,
|
|
1287
1315
|
state: stateRef.current,
|
|
1288
1316
|
dispatch: (action) => {
|
|
@@ -1306,7 +1334,11 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1306
1334
|
toast,
|
|
1307
1335
|
persistConfig,
|
|
1308
1336
|
exit: doExit,
|
|
1309
|
-
submit: (text, opts) =>
|
|
1337
|
+
submit: (text, opts) => {
|
|
1338
|
+
const result = submitMessage(text, opts);
|
|
1339
|
+
if (!result.accepted)
|
|
1340
|
+
throw new Error(result.reason ?? 'Message was not accepted.');
|
|
1341
|
+
},
|
|
1310
1342
|
refreshSkills: () => setSkillsNonce((n) => n + 1),
|
|
1311
1343
|
applyAgentMode: applyMode,
|
|
1312
1344
|
// Read at command time, so `/todo status` reports the live count rather than
|
|
@@ -1361,28 +1393,33 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1361
1393
|
* start with `/`, recurse into command dispatch.
|
|
1362
1394
|
*/
|
|
1363
1395
|
const submitMessage = (message, opts = {}) => {
|
|
1396
|
+
if (controller.getConfig().modelSettingsRestartRequired) {
|
|
1397
|
+
return { accepted: false, reason: 'Settings require a restart before sending requests.' };
|
|
1398
|
+
}
|
|
1399
|
+
if (interactionPhase.current === 'starting') {
|
|
1400
|
+
return { accepted: false, reason: 'A run is still starting. Press Esc twice to cancel.' };
|
|
1401
|
+
}
|
|
1364
1402
|
// A USER MESSAGE IS A NEW INTENT AND OUTRANKS A QUEUED ONE (AC-19). First
|
|
1365
1403
|
// line, before the steering branch: a message typed during the grace window
|
|
1366
1404
|
// must be the one that runs, whichever branch it takes.
|
|
1367
1405
|
cancelFollowThrough();
|
|
1368
1406
|
cancelConfirmation();
|
|
1369
1407
|
if (interactionPhase.current === 'running') {
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
toast('info', 'A run is still starting. Press Esc twice to cancel.');
|
|
1381
|
-
return;
|
|
1408
|
+
try {
|
|
1409
|
+
const queueId = controller.queueUserMessage(message);
|
|
1410
|
+
dispatch({ type: 'steerQueued', queueId, text: message });
|
|
1411
|
+
return { accepted: true };
|
|
1412
|
+
}
|
|
1413
|
+
catch (error) {
|
|
1414
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
1415
|
+
notify('error', reason);
|
|
1416
|
+
return { accepted: false, reason };
|
|
1417
|
+
}
|
|
1382
1418
|
}
|
|
1383
1419
|
resetInterrupt();
|
|
1384
1420
|
const sequence = ++submissionSequence.current;
|
|
1385
1421
|
interactionPhase.current = 'starting';
|
|
1422
|
+
dispatch({ type: 'runStarting' });
|
|
1386
1423
|
// `userInitiated: false` skips TWO things and both are wanted: the
|
|
1387
1424
|
// prompt-history append, and the `submitCount` bump that drives the
|
|
1388
1425
|
// composer's hint fade. An auto-continuation demonstrates nothing about what
|
|
@@ -1396,12 +1433,13 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1396
1433
|
dispatch({ type: 'submit', text: message });
|
|
1397
1434
|
const pre = controller.preflight();
|
|
1398
1435
|
if (!pre.ok) {
|
|
1436
|
+
dispatch({ type: 'runError' });
|
|
1399
1437
|
notify('error', pre.message ?? 'Configuration error.');
|
|
1400
1438
|
interactionPhase.current = 'idle';
|
|
1401
1439
|
resetInterrupt();
|
|
1402
1440
|
autoContinuationRef.current = false;
|
|
1403
1441
|
dispatch({ type: 'runEnd' }); // reset status; nothing was started
|
|
1404
|
-
return;
|
|
1442
|
+
return { accepted: true };
|
|
1405
1443
|
}
|
|
1406
1444
|
// Fire-and-forget: events drive the UI. prompt() never rejects.
|
|
1407
1445
|
void controller.prompt(message, { todoPolicy: opts.todoPolicy ?? 'new-task' }).then((outcome) => {
|
|
@@ -1409,110 +1447,63 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1409
1447
|
return;
|
|
1410
1448
|
if (outcome?.status !== 'not-started')
|
|
1411
1449
|
return;
|
|
1450
|
+
if (outcome.reason !== 'cancelled')
|
|
1451
|
+
dispatch({ type: 'runError' });
|
|
1412
1452
|
interactionPhase.current = 'idle';
|
|
1413
1453
|
resetInterrupt();
|
|
1414
1454
|
autoContinuationRef.current = false;
|
|
1415
1455
|
dispatch({ type: 'runEnd' });
|
|
1416
1456
|
});
|
|
1457
|
+
return { accepted: true };
|
|
1417
1458
|
};
|
|
1418
1459
|
submitRef.current = submitMessage;
|
|
1419
|
-
const
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1460
|
+
const executeSlashInput = async (raw) => {
|
|
1461
|
+
// Retain a recoverable command before any asynchronous handler can fail.
|
|
1462
|
+
const safe = redactText(raw, getSecrets());
|
|
1463
|
+
recordPrompt(safe);
|
|
1464
|
+
notify('info', `Command: ${safe}`);
|
|
1465
|
+
try {
|
|
1466
|
+
await runSlashInput(registry, raw, makeCtx);
|
|
1467
|
+
}
|
|
1468
|
+
catch (error) {
|
|
1469
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
1470
|
+
notify('error', `Command failed: ${redactText(reason, getSecrets())}\nInput: ${safe}`);
|
|
1471
|
+
}
|
|
1472
|
+
};
|
|
1473
|
+
const handleSubmit = (raw) => {
|
|
1474
|
+
const trimmed = raw.trimStart();
|
|
1475
|
+
const slash = trimmed.startsWith('/') && !trimmed.startsWith('//');
|
|
1476
|
+
if (slash && (interactionPhase.current === 'running' || controller.isRunning()) &&
|
|
1477
|
+
/^\/(reset|resume)(?:\s|$)/.test(trimmed)) {
|
|
1478
|
+
return { accepted: false,
|
|
1479
|
+
reason: 'Interrupt the current run before switching conversations.' };
|
|
1423
1480
|
}
|
|
1424
1481
|
// Submitting is an unconditional "take me to the newest output" (§4.5).
|
|
1425
1482
|
setPinToBottomNonce((n) => n + 1);
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
return;
|
|
1429
|
-
|
|
1483
|
+
if (slash) {
|
|
1484
|
+
void executeSlashInput(raw);
|
|
1485
|
+
return { accepted: true };
|
|
1486
|
+
}
|
|
1487
|
+
return submitMessage(trimmed.startsWith('//') ? raw.replace('//', '/') : raw);
|
|
1430
1488
|
};
|
|
1431
|
-
const
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
controller.setThinkingLevel(values.thinkingLevel);
|
|
1438
|
-
controller.setMaxTokens(cap.applied);
|
|
1439
|
-
// THE SETTER AND THE PERSIST ARE BOTH REQUIRED, the pair `/todo panel`
|
|
1440
|
-
// documents: persisting alone reports success and changes nothing until the
|
|
1441
|
-
// next launch. `thinkingVisible` lives in `ViewState`, not in the config the
|
|
1442
|
-
// controller holds, so the live half is a dispatch rather than a setter —
|
|
1443
|
-
// and it is a no-op when the row already agrees with the current state.
|
|
1444
|
-
if ((values.showThinking === 'on') !== stateRef.current.thinkingVisible) {
|
|
1489
|
+
const handleProfileSave = (draft) => {
|
|
1490
|
+
const result = controller.saveModelSettings(draft);
|
|
1491
|
+
if (!result.ok)
|
|
1492
|
+
return result;
|
|
1493
|
+
if (draft.patch.showThinking !== undefined
|
|
1494
|
+
&& controller.getConfig().showThinking !== stateRef.current.thinkingVisible) {
|
|
1445
1495
|
dispatch({ type: 'toggleThinking' });
|
|
1446
1496
|
}
|
|
1447
|
-
const key = values.apiKey.trim();
|
|
1448
|
-
if (key.length > 0) {
|
|
1449
|
-
// Registration site 4 (§4.4.3), and the one that matters most: this is how
|
|
1450
|
-
// a key most often enters the process, and it happens long after
|
|
1451
|
-
// `installLogging()` ran. A startup snapshot would miss it entirely, and
|
|
1452
|
-
// the vendor regexes only cover keys that look like a vendor's.
|
|
1453
|
-
registerSecret(key);
|
|
1454
|
-
controller.setApiKey(values.provider, key);
|
|
1455
|
-
}
|
|
1456
|
-
const patch = {
|
|
1457
|
-
provider: values.provider,
|
|
1458
|
-
model: values.model,
|
|
1459
|
-
baseUrl: values.baseUrl.trim() ? values.baseUrl.trim() : null,
|
|
1460
|
-
thinkingLevel: values.thinkingLevel,
|
|
1461
|
-
showThinking: values.showThinking === 'on',
|
|
1462
|
-
// PERSIST ONLY, with no paired live setter — the store is allocated in
|
|
1463
|
-
// `AgentController`'s constructor, so this row takes effect on the next
|
|
1464
|
-
// launch. That is what manual row 8 asks for, and it is why the seed above
|
|
1465
|
-
// reads `cfg` rather than `ViewState`.
|
|
1466
|
-
liveToolOutput: values.liveToolOutput === 'on',
|
|
1467
|
-
// Omitted entirely when the field was unusable, so the previous value
|
|
1468
|
-
// survives instead of being overwritten with a guess.
|
|
1469
|
-
...(cap.persist !== 'skip' ? { maxTokens: cap.persist } : {}),
|
|
1470
|
-
};
|
|
1471
|
-
if (key.length > 0)
|
|
1472
|
-
patch.apiKeys = { [values.provider]: key };
|
|
1473
|
-
// A PARTIAL `log` section, safe only because `updatePersistedConfig` deep-
|
|
1474
|
-
// merges it — a shallow merge here would reset `redactSecrets` every time
|
|
1475
|
-
// someone touched the level.
|
|
1476
|
-
patch.log = { level: values.logLevel };
|
|
1477
|
-
// --- Fast tier (fast-model-tier §4.5) ---------------------------------
|
|
1478
|
-
//
|
|
1479
|
-
// THE SETTER AND THE PERSIST ARE BOTH REQUIRED, and this is the pair `/todo
|
|
1480
|
-
// panel` documents (P1-2): `App` reads `controller.getConfig()` at render
|
|
1481
|
-
// time, so persisting alone reports success and changes nothing until the
|
|
1482
|
-
// next launch, while the setter alone forgets by morning.
|
|
1483
|
-
//
|
|
1484
|
-
// `setFastConfig` also re-resolves the tier, which matters HERE more than
|
|
1485
|
-
// anywhere: this same Enter press may have changed the main provider, the
|
|
1486
|
-
// main model or the API key, and `fast.provider: ''` inherits from all
|
|
1487
|
-
// three (§3.2 rule 8 / RV-3).
|
|
1488
|
-
const fastPatch = readFastSettings(values);
|
|
1489
|
-
controller.setFastConfig(fastPatch);
|
|
1490
|
-
patch.fast = fastPatch;
|
|
1491
|
-
// --- Context compaction (context-auto-compaction §4.5) ------------------
|
|
1492
|
-
//
|
|
1493
|
-
// THE SAME SETTER-AND-PERSIST PAIR, and the same reason. `setCompactionConfig`
|
|
1494
|
-
// additionally re-emits the snapshot, which is what moves the status chip and
|
|
1495
|
-
// the gauge's marks without waiting for the next turn.
|
|
1496
|
-
//
|
|
1497
|
-
// `enabled` here flips the LIVE switch only. Whether the wiring exists at all
|
|
1498
|
-
// was decided at construction and cannot be undone in this session (§3.2), so
|
|
1499
|
-
// a user who turns it ON in a `--no-compaction` session gets the persisted
|
|
1500
|
-
// value for next launch and nothing this session — which is exactly what
|
|
1501
|
-
// `/compact on` reports in words.
|
|
1502
|
-
const compactionPatch = readCompactionSettings(values);
|
|
1503
|
-
controller.setCompactionConfig(compactionPatch);
|
|
1504
|
-
if (compactionPatch.enabled !== undefined) {
|
|
1505
|
-
controller.setCompactionEnabled(compactionPatch.enabled);
|
|
1506
|
-
}
|
|
1507
|
-
patch.compaction = compactionPatch;
|
|
1508
|
-
persistConfig(patch);
|
|
1509
|
-
getLogger().reconfigure({ ...controller.getConfig().log, level: values.logLevel });
|
|
1510
1497
|
dispatch({ type: 'setOverlay', overlay: null });
|
|
1511
|
-
toast(
|
|
1498
|
+
toast(result.restartRequired ? 'warn' : 'success', modelSettingsResultMessage(result));
|
|
1499
|
+
return result;
|
|
1512
1500
|
};
|
|
1513
1501
|
const handleModelSelect = (provider, model) => {
|
|
1514
|
-
controller.
|
|
1515
|
-
|
|
1502
|
+
const result = controller.saveModelSettingsPatch({ provider, model });
|
|
1503
|
+
if (!result.ok) {
|
|
1504
|
+
toast('error', modelSettingsResultMessage(result));
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1516
1507
|
dispatch({ type: 'setOverlay', overlay: null });
|
|
1517
1508
|
toast('info', `Model set to ${provider}:${model}.`);
|
|
1518
1509
|
};
|
|
@@ -1619,15 +1610,22 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1619
1610
|
// (`via === 'none'`) still reports through the shared toast funnel and
|
|
1620
1611
|
// returns: the selection is already consumed, and the NEXT Ctrl+C
|
|
1621
1612
|
// walks the original ladder.
|
|
1613
|
+
if (key.ctrl && (input === 'c' || input === 'C') && copyCoordinator.isBusy()) {
|
|
1614
|
+
void copyCoordinator.requestCopy({ text: '', lines: 0 });
|
|
1615
|
+
return;
|
|
1616
|
+
}
|
|
1622
1617
|
if (key.ctrl &&
|
|
1623
1618
|
(input === 'c' || input === 'C') &&
|
|
1624
1619
|
(selectionController?.hasPendingSelection() ?? false)) {
|
|
1625
1620
|
const payload = selectionController.takeSelection();
|
|
1621
|
+
ctrlCArmed.current = false;
|
|
1622
|
+
if (ctrlCTimer.current)
|
|
1623
|
+
clearTimeout(ctrlCTimer.current);
|
|
1624
|
+
ctrlCTimer.current = null;
|
|
1626
1625
|
if (payload) {
|
|
1627
|
-
|
|
1628
|
-
selectionBridge?.onCopied?.(via, payload.lines, payload.text.length);
|
|
1629
|
-
return; // copied -- no service stop, no arm, no clear
|
|
1626
|
+
void copyCoordinator.requestCopy({ text: payload.text, lines: payload.lines });
|
|
1630
1627
|
}
|
|
1628
|
+
return; // A consumed copy intent never becomes exit or service stop.
|
|
1631
1629
|
}
|
|
1632
1630
|
selectionController?.clear();
|
|
1633
1631
|
if (key.ctrl && (input === 'c' || input === 'C')) {
|
|
@@ -1672,7 +1670,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1672
1670
|
const overlay = stateRef.current.overlay;
|
|
1673
1671
|
// Modes A only. `model` / `confirm` manage their own keys; registering an
|
|
1674
1672
|
// arrow handler for them would double every keypress (R-12).
|
|
1675
|
-
const controlled = overlay === 'help' || overlay === 'settings' || overlay === 'plan';
|
|
1673
|
+
const controlled = overlay === 'help' || overlay === 'settings' || overlay === 'plan' || overlay === 'queue';
|
|
1676
1674
|
if (key.pageUp) {
|
|
1677
1675
|
if (controlled)
|
|
1678
1676
|
setOverlayScroll((n) => Math.max(0, n - OVERLAY_PAGE));
|
|
@@ -1687,7 +1685,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1687
1685
|
// worth more than scrolling six fields (§4.9). `question` keeps them for
|
|
1688
1686
|
// its option cursor, for the same reason; `plan` has no cursor of its own
|
|
1689
1687
|
// and scrolls a line at a time.
|
|
1690
|
-
if ((overlay === 'help' || overlay === 'plan') && (key.upArrow || key.downArrow)) {
|
|
1688
|
+
if ((overlay === 'help' || overlay === 'plan' || overlay === 'queue') && (key.upArrow || key.downArrow)) {
|
|
1691
1689
|
setOverlayScroll((n) => Math.max(0, n + (key.upArrow ? -1 : 1)));
|
|
1692
1690
|
return;
|
|
1693
1691
|
}
|
|
@@ -1782,6 +1780,8 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1782
1780
|
}
|
|
1783
1781
|
if (key.escape) {
|
|
1784
1782
|
const overlay = stateRef.current.overlay;
|
|
1783
|
+
if (overlay === 'settings')
|
|
1784
|
+
return;
|
|
1785
1785
|
if (overlay) {
|
|
1786
1786
|
cancelConfirmation();
|
|
1787
1787
|
if (overlay === 'confirm' && confirmRef.current) {
|
|
@@ -1816,7 +1816,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1816
1816
|
interruptGesture.current = next.state;
|
|
1817
1817
|
if (next.action === 'hint') {
|
|
1818
1818
|
dispatch({ type: 'pushToast', level: 'info',
|
|
1819
|
-
text: '
|
|
1819
|
+
text: '\u4e2d\u65ad\u5df2\u51c6\u5907' });
|
|
1820
1820
|
return;
|
|
1821
1821
|
}
|
|
1822
1822
|
endReasonRef.current.aborted = true;
|
|
@@ -1837,7 +1837,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1837
1837
|
controller.abort();
|
|
1838
1838
|
if (sequence === submissionSequence.current && interactionPhase.current === 'running') {
|
|
1839
1839
|
dispatch({ type: 'pushToast', level: 'info',
|
|
1840
|
-
text: '
|
|
1840
|
+
text: '\u6b63\u5728\u505c\u6b62' });
|
|
1841
1841
|
}
|
|
1842
1842
|
}, [controller, cancelFollowThrough, resetInterrupt]);
|
|
1843
1843
|
// --- Render ------------------------------------------------------------
|
|
@@ -1848,6 +1848,8 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1848
1848
|
// which is what lets the user's `contextWindow` override reach the gauge at
|
|
1849
1849
|
// all. Nothing else in this render read them.
|
|
1850
1850
|
const overlay = state.overlay;
|
|
1851
|
+
const settingsDraft = useMemo(() => overlay === 'settings'
|
|
1852
|
+
? controller.createModelSettingsDraft() : undefined, [controller, overlay]);
|
|
1851
1853
|
const hasKey = controller.hasApiKey();
|
|
1852
1854
|
const empty = state.entries.length === 0;
|
|
1853
1855
|
// RENDER SCOPE, so the chip and the composer hint agree with what the Ctrl+C
|
|
@@ -1896,24 +1898,28 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1896
1898
|
const nowSec = running || compactionLive ? Math.floor(Date.now() / 1000) : undefined;
|
|
1897
1899
|
const viewportBudget = computeViewportRows(rows, draftRows);
|
|
1898
1900
|
const overlayMaxRows = viewportBudget;
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
const
|
|
1901
|
+
const composerBaseRows = 2 + clampDraftRows(rows, draftRows);
|
|
1902
|
+
const baseRail = buildTodoRailLayout({
|
|
1903
|
+
cols: cols - 1, panelEnabled: cfg.todo.panel, overlayOpen: false,
|
|
1904
|
+
itemCount: state.todos?.items.length ?? 0, viewportBudget,
|
|
1905
|
+
teamRows: 0, popupRows: 0, composerBaseRows,
|
|
1906
|
+
});
|
|
1907
|
+
const queueLayout = useMemo(() => buildQueueLayout({
|
|
1908
|
+
pending: state.pendingSteering, columns: baseRail.contentCols, terminalRows: rows,
|
|
1909
|
+
availableRows: Math.max(0, viewportBudget - composerBaseRows - 1), paused: !running,
|
|
1910
|
+
}), [state.pendingSteering, baseRail.contentCols, rows, viewportBudget, composerBaseRows, running]);
|
|
1906
1911
|
const teamLayout = buildTeamPanelLayout({
|
|
1907
|
-
snapshot: state.team, terminalRows: rows,
|
|
1912
|
+
snapshot: state.team, terminalRows: rows,
|
|
1913
|
+
availableRows: Math.max(0, viewportBudget - composerBaseRows - queueLayout.rows - 1),
|
|
1908
1914
|
});
|
|
1909
1915
|
const railLayout = buildTodoRailLayout({
|
|
1910
1916
|
cols: cols - 1, panelEnabled: cfg.todo.panel, overlayOpen: false,
|
|
1911
1917
|
itemCount: state.todos?.items.length ?? 0, viewportBudget,
|
|
1912
|
-
teamRows: teamLayout.rowCount, popupRows, composerBaseRows,
|
|
1918
|
+
teamRows: teamLayout.rowCount, queueRows: queueLayout.rows, popupRows, composerBaseRows,
|
|
1913
1919
|
});
|
|
1914
|
-
// Transcript and composer share contentCols; fixed chrome and overlays use terminal width.
|
|
1915
1920
|
const { visible: showRail, width: railWidth, rows: railRows, contentCols } = railLayout;
|
|
1916
|
-
const
|
|
1921
|
+
const transcriptEntries = useMemo(() => selectTranscriptEntries(state.entries, state.pendingSteering), [state.entries, state.pendingSteering]);
|
|
1922
|
+
const overlayNode = overlay === 'queue' ? (_jsx(QueueOverlay, { pending: state.pendingSteering, cols: cols, maxRows: overlayMaxRows, scrollOffset: overlayScroll, onScrollClamp: setOverlayScroll, theme: theme, caps: caps })) : overlay === 'help' ? (_jsx(OverlayFrame, { title: "Help", hint: `Esc close ${glyphs.midDot} PgUp/PgDn scroll`, maxRows: overlayMaxRows, cols: cols, rows: helpRows(theme, caps), scrollOffset: overlayScroll, onScrollClamp: setOverlayScroll, theme: theme, caps: caps })) : overlay === 'model' ? (_jsx(ModelPicker, { registry: controller.getModelRegistry(), currentProvider: cfg.provider, currentModel: cfg.model, maxRows: overlayMaxRows, cols: cols, theme: theme, caps: caps, onSelect: handleModelSelect })) : overlay === 'settings' ? (_jsx(SettingsScreen, { initial: {
|
|
1917
1923
|
provider: cfg.provider,
|
|
1918
1924
|
model: cfg.model,
|
|
1919
1925
|
baseUrl: cfg.baseUrl ?? '',
|
|
@@ -1942,7 +1948,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1942
1948
|
// the save handler cannot disagree about what `(inherit)` means.
|
|
1943
1949
|
...fastSettingsFrom(cfg.fast),
|
|
1944
1950
|
...compactionSettingsFrom(cfg.compaction),
|
|
1945
|
-
}, apiKeys: cfg.apiKeys, maxRows: overlayMaxRows, cols: cols, scrollOffset: overlayScroll, onScrollClamp: setOverlayScroll, theme: theme, caps: caps,
|
|
1951
|
+
}, apiKeys: cfg.apiKeys, maxRows: overlayMaxRows, cols: cols, scrollOffset: overlayScroll, onScrollClamp: setOverlayScroll, theme: theme, caps: caps, draft: settingsDraft, onProfileSave: handleProfileSave, onReload: () => controller.createModelSettingsDraft(), onClose: () => dispatch({ type: 'setOverlay', overlay: null }),
|
|
1946
1952
|
// Read from `cfg` (render scope, re-read every frame), so `/retry off`
|
|
1947
1953
|
// while the screen is open shows the new value on the next paint.
|
|
1948
1954
|
retrySummary: cfg.retry.enabled && cfg.retry.maxRetries > 0 ? String(cfg.retry.maxRetries) : 'off' })) : overlay === 'confirm' && confirmState ? (_jsx(ConfirmDialog, { state: confirmState, maxRows: overlayMaxRows, cols: cols, theme: theme, caps: caps, onClose: closeConfirm })) : overlay === 'question' && humanRequest?.kind === 'questions' ? (_jsx(QuestionOverlay, { questions: humanRequest.questions, maxRows: overlayMaxRows, cols: cols, theme: theme, caps: caps, onSubmit: (answers) => resolveHuman({ kind: 'answers', answers, cancelled: false }) })) : overlay === 'plan' && humanRequest?.kind === 'plan' ? (_jsx(PlanReviewOverlay, { plan: humanRequest.plan, maxRows: overlayMaxRows, cols: cols, scrollOffset: overlayScroll, onScrollClamp: setOverlayScroll, theme: theme, caps: caps, onVerdict: handlePlanVerdict })) : null;
|
|
@@ -1953,61 +1959,8 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1953
1959
|
const opener = empty ? (_jsx(SessionOpener, { variant: pickOpenerVariant(viewportBudget, contentCols, caps), version: version, cwd: controller.getCwd(), hasKey: hasKey, viewportRows: viewportBudget, theme: theme, caps: caps })) : null;
|
|
1954
1960
|
const density = cfg.density;
|
|
1955
1961
|
const transcriptWindow = cfg.transcriptWindow ?? DEFAULT_TRANSCRIPT_WINDOW;
|
|
1956
|
-
//
|
|
1957
|
-
|
|
1958
|
-
// THE MOUNT CONDITION, NAMED ONCE AND USED TWICE (D-4). This const both gates
|
|
1959
|
-
// the `<ActivityLine>` element below and feeds the derivation on the next
|
|
1960
|
-
// line, so "the line is up" and "every other site is still" can never
|
|
1961
|
-
// disagree. Two copies of the same boolean expression is exactly how that
|
|
1962
|
-
// invariant becomes false in six months, silently and in only one branch.
|
|
1963
|
-
//
|
|
1964
|
-
// NOT `running` ALONE (D-3). While an overlay owns the screen the activity row
|
|
1965
|
-
// is suppressed, so keying suppression off `running` would leave the frame
|
|
1966
|
-
// with ZERO life signals: the bar shows state, this row shows life. Never
|
|
1967
|
-
// trade one duplicate for one absence.
|
|
1968
|
-
//
|
|
1969
|
-
// THE CLAIM ABOVE WAS ONCE FALSE, AND THAT WAS THE WHOLE OF
|
|
1970
|
-
// `activity-spinner-vanishes-behind-toast`. `BottomStatusRow` — not this const
|
|
1971
|
-
// — decides what the row actually shows, and a toast used to take the row
|
|
1972
|
-
// outright, so for its 2.5 s TTL this said "the line is up" while nothing was
|
|
1973
|
-
// mounted and all seven other sites sat still. The answer was NOT to enumerate
|
|
1974
|
-
// `&& !state.toasts.length` here (the next occupant reopens it, and the seven
|
|
1975
|
-
// sites animating out of phase is the defect this whole feature removed):
|
|
1976
|
-
// `BottomStatusRow` now carries the bare spinner alongside the toast, so the
|
|
1977
|
-
// signal is mounted exactly when this const is true and the claim holds by
|
|
1978
|
-
// construction.
|
|
1979
|
-
const activityVisible = running && !overlayNode;
|
|
1980
|
-
// The FOUR VIEW CONSUMERS (transcript x 2, team panel, todo panel) take this;
|
|
1981
|
-
// the activity line keeps the raw config flag (D-5), because it reads that
|
|
1982
|
-
// flag a second time to gate phrase rotation and the widened value would
|
|
1983
|
-
// freeze the phrase at the first word of every run.
|
|
1984
|
-
//
|
|
1985
|
-
// So below `App` the prop now means "this frame does not animate here" rather
|
|
1986
|
-
// than "the user asked for reduced motion". `cfg.reducedMotion` (`:279`) stays
|
|
1987
|
-
// the sole config source, and `spinner-census.test.ts` pins this wiring —
|
|
1988
|
-
// threading the raw flag into a fifth consumer is the one silent way back to
|
|
1989
|
-
// two spinners (R-2).
|
|
1990
|
-
const viewReducedMotion = reducedMotion || activityVisible;
|
|
1991
|
-
// The run status row (spinner + phrase + queue/interrupt hint) replaces the idle
|
|
1992
|
-
// hint row ABOVE the input while running; it exists only when that hint row
|
|
1993
|
-
// would, so the footer height is identical with and without it. `running`, not
|
|
1994
|
-
// `activityVisible`: an overlay must not flip the Composer between two layouts.
|
|
1995
|
-
const runRowEnabled = running && hintRowEnabled;
|
|
1996
|
-
// The raw `reducedMotion` flag lives here and nowhere else (D-5).
|
|
1997
|
-
const runActivity = {
|
|
1998
|
-
startedAt: runStartedAt.current,
|
|
1999
|
-
elapsedMs,
|
|
2000
|
-
reducedMotion,
|
|
2001
|
-
runningTool,
|
|
2002
|
-
// THE ROW MUST NEVER SAY SOMETHING FALSE (§6.1). Compaction runs BEFORE
|
|
2003
|
-
// `turn_start`, so `status` is `'running'` with no tool in flight - precisely
|
|
2004
|
-
// the window in which the rotating phrase would claim the model is thinking
|
|
2005
|
-
// while it is actually waiting on a 30k-token summarization.
|
|
2006
|
-
compacting: state.compaction?.inFlight === true,
|
|
2007
|
-
};
|
|
2008
|
-
// The fixed bottom row takes over the life signal once the run row scrolls out
|
|
2009
|
-
// of the viewport; exactly one of the two ever animates.
|
|
2010
|
-
const runRowShown = runRowEnabled && activityRowVisible;
|
|
1962
|
+
// The fixed status bar remains visible underneath every overlay and toast.
|
|
1963
|
+
const viewReducedMotion = reducedMotion || running || state.runPhase === 'starting' || compactionLive;
|
|
2011
1964
|
// Assigned during render so `/perf` reads the CURRENT frame (see `perfRef`).
|
|
2012
1965
|
// `mountedEntries` is the same number `render-budget.test.tsx` asserts (AC-9).
|
|
2013
1966
|
perfRef.current = {
|
|
@@ -2044,39 +1997,21 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2044
1997
|
// Both keys always WORK (they share one branch in the input handler); this
|
|
2045
1998
|
// only decides which one the user is taught.
|
|
2046
1999
|
const modeToggleKey = vtInputWarning ? MODE_TOGGLE_KEYS.fallback : MODE_TOGGLE_KEYS.primary;
|
|
2047
|
-
const composer = (_jsx(Composer, { measureRef: composerRef,
|
|
2048
|
-
? { activity: runActivity, live: runRowShown && !overlayNode, rowRef: activityRef }
|
|
2049
|
-
: null, cols: contentCols, cursorVisible: cursorVisible, 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: rows >= HINT_MIN_ROWS, submitCount: cfg.submitCount, hintsEnabled: cfg.hints, agentMode: state.agentMode, services: liveServices, modeToggleKey: modeToggleKey, popupMaxRows: popupMaxRows, popupMaxHeight: railLayout.popupMaxHeight, onPopupRowsChange: onPopupRowsChange, scrolledLines: scrolledLines, onDraftRows: onDraftRows, onNotice: notify, theme: theme, caps: caps, onSubmit: (text) => void handleSubmit(text), onHelp: () => dispatch({ type: 'setOverlay', overlay: 'help' }) }));
|
|
2000
|
+
const composer = (_jsx(Composer, { measureRef: composerRef, deleteDisambiguated: terminal?.deleteDisambiguated ?? false, onCompletionContextChange: setCompletion, cols: contentCols, cursorVisible: cursorVisible, 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: rows >= HINT_MIN_ROWS, submitCount: cfg.submitCount, hintsEnabled: cfg.hints, agentMode: state.agentMode, services: liveServices, modeToggleKey: modeToggleKey, popupMaxRows: popupMaxRows, popupMaxHeight: railLayout.popupMaxHeight, onPopupRowsChange: onPopupRowsChange, scrolledLines: scrolledLines, onDraftRows: onDraftRows, onNotice: notify, theme: theme, caps: caps, onSubmit: handleSubmit, onHelp: () => dispatch({ type: 'setOverlay', overlay: 'help' }) }));
|
|
2050
2001
|
const team = state.team ? (_jsx(TeamPanel, { snapshot: state.team, layout: teamLayout, rows: rows, cols: contentCols, reducedMotion: viewReducedMotion, theme: theme, caps: caps })) : null;
|
|
2051
2002
|
const rail = showRail && state.todos ? (_jsx(TodoPanel, { snapshot: state.todos, width: railWidth, rows: railRows, running: running, reducedMotion: viewReducedMotion, theme: theme, caps: caps })) : null;
|
|
2052
|
-
const viewport = (_jsxs(ScrollViewport, { active: overlay === null && !tooSmall, overlay: tooSmall ? null : overlayNode, footer: _jsxs(_Fragment, { children: [team, composer] }), rail: rail, scrollbar: terminal?.scrollbar, composerRef: composerRef, onComposerVisibilityChange: setCursorVisible, activityRef: activityRef, onActivityVisibilityChange: setActivityRowVisible, resetKey: scrollResetKey, intent: scrollIntent, pinToBottomNonce: pinToBottomNonce, onScrolledLinesChange: setScrolledLines, onViewportShiftChange: onViewportShiftChange, tailRowsRef: tailSink, hold: selectionHold, resumeMs: cfg.scrollResumeMs, showScrollIndicator: true, cols: contentCols, theme: theme, caps: caps, children: [opener, _jsx(TranscriptList, { entries:
|
|
2053
|
-
return (_jsx(AppShell, { inactive: tooSmall, placeholder: _jsxs(Text, { wrap: "truncate", color: theme.noticeWarn, children: [glyphs.warn, " Terminal too small - needs at least 40 columns and 12 rows."] }), rows: rows, cols: cols, header: header, viewport: viewport, toast: _jsx(BottomStatusRow, { toasts: state.toasts,
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
// prop for truthiness, and a React element is truthy however it
|
|
2065
|
-
// renders - so a component that rendered nothing would give the row
|
|
2066
|
-
// ZERO rows and silently unbalance the frame budget. Same rule the
|
|
2067
|
-
// activity line above follows with `running && !overlayNode`.
|
|
2068
|
-
//
|
|
2069
|
-
// `!overlayNode` for the same reason it appears there: the full-screen
|
|
2070
|
-
// branch replaces the whole viewport with the overlay, and an update
|
|
2071
|
-
// notice under a settings screen is noise attached to a surface it is
|
|
2072
|
-
// not visible on.
|
|
2073
|
-
//
|
|
2074
|
-
// `!running` (cli-auto-update D-2): the update notice stays silent for
|
|
2075
|
-
// the whole run. It used to be guaranteed by the activity line
|
|
2076
|
-
// out-ranking it inside this row; with the activity line moved above
|
|
2077
|
-
// the input, the row can be free during a run, so the silence is stated
|
|
2078
|
-
// here instead of inherited.
|
|
2079
|
-
update: updateSnapshot && !overlayNode && !running && shouldRenderUpdateLine(updateSnapshot) ? (_jsx(UpdateLine, { snapshot: updateSnapshot, compact: cols < UPDATE_LIMITS.statusCompactCols, theme: theme, caps: caps })) : null, theme: theme }), status: _jsx(StatusBar, { interruptHint: interruptGesture.current.phase === 'stopping'
|
|
2003
|
+
const viewport = (_jsxs(ScrollViewport, { active: overlay === null && !tooSmall, overlay: tooSmall ? null : overlayNode, footer: _jsxs(_Fragment, { children: [team, _jsx(QueuePanel, { layout: queueLayout, theme: theme }), composer] }), rail: rail, scrollbar: terminal?.scrollbar, composerRef: composerRef, onComposerVisibilityChange: setCursorVisible, activityRef: activityRef, onActivityVisibilityChange: setActivityRowVisible, resetKey: scrollResetKey, intent: scrollIntent, pinToBottomNonce: pinToBottomNonce, onScrolledLinesChange: setScrolledLines, onViewportShiftChange: onViewportShiftChange, 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 })] }));
|
|
2004
|
+
return (_jsx(AppShell, { inactive: tooSmall, placeholder: _jsxs(Text, { wrap: "truncate", color: theme.noticeWarn, children: [glyphs.warn, " Terminal too small - needs at least 40 columns and 12 rows."] }), rows: rows, cols: cols, header: header, viewport: viewport, toast: _jsx(BottomStatusRow, { toasts: state.toasts, hints: { interactionPhase: state.runPhase === 'starting' ? 'starting'
|
|
2005
|
+
: running ? 'running' : 'idle', cols, completion,
|
|
2006
|
+
selectionPending: selectionController?.hasPendingSelection() ?? false,
|
|
2007
|
+
copyInFlight: copyState.busy, copyCleanupPending: copyState.cleanupPending,
|
|
2008
|
+
overlay, services: liveServices, hintsEnabled: cfg.hints, modeToggleKey,
|
|
2009
|
+
interruptHint: interruptGesture.current.phase === 'stopping'
|
|
2010
|
+
? 'Esc force-stop' : interruptGesture.current.phase === 'armed'
|
|
2011
|
+
? 'Esc confirm' : 'Esc x2 interrupt',
|
|
2012
|
+
updateAvailable: !!updateSnapshot && !overlayNode && !running
|
|
2013
|
+
&& shouldRenderUpdateLine(updateSnapshot),
|
|
2014
|
+
}, update: updateSnapshot && !overlayNode && !running && shouldRenderUpdateLine(updateSnapshot) ? (_jsx(UpdateLine, { snapshot: updateSnapshot, compact: cols < UPDATE_LIMITS.statusCompactCols, theme: theme, caps: caps })) : null, theme: theme }), status: _jsx(StatusBar, { pendingSteering: state.pendingSteering, runPhase: state.runPhase, activeTool: state.activeTool, runOutcome: state.runOutcome, reducedMotion: reducedMotion, waitingForConfirmation: !!humanRequest || !!confirmState, stopping: interruptGesture.current.phase === 'stopping', interruptHint: interruptGesture.current.phase === 'stopping'
|
|
2080
2015
|
? 'Esc force-stop' : 'Esc x2 interrupt', model: cfg.model, provider: cfg.provider, usageTotal: state.usageTotal,
|
|
2081
2016
|
// ONE OBJECT INSTEAD OF FOUR PARALLEL PROPS (§4.3). The four were a
|
|
2082
2017
|
// projection of "many sources of truth" into the props layer, and a
|