@aragon-agent/cli 0.6.12 → 0.6.14
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 +27 -0
- package/README.md +108 -31
- package/dist/agent/controller.d.ts +38 -2
- package/dist/agent/controller.js +185 -34
- package/dist/agent/model-profile-settings.d.ts +31 -0
- package/dist/agent/model-profile-settings.js +208 -0
- package/dist/agent/queued-messages.d.ts +12 -0
- package/dist/agent/queued-messages.js +18 -0
- package/dist/agent/reducer.d.ts +13 -1
- package/dist/agent/reducer.js +52 -12
- package/dist/boot/guard.d.ts +3 -3
- package/dist/boot/guard.js +3 -3
- package/dist/cli.js +48 -16
- package/dist/commands/builtins.js +66 -39
- 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 +8 -1
- package/dist/compaction/compactor.d.ts +7 -2
- package/dist/compaction/compactor.js +21 -16
- package/dist/compaction/context-command.js +10 -3
- package/dist/compaction/meter.js +2 -0
- package/dist/compaction/pressure.d.ts +1 -0
- package/dist/compaction/pressure.js +1 -0
- package/dist/compaction/types.d.ts +2 -0
- package/dist/compaction/wiring.d.ts +4 -3
- package/dist/compaction/wiring.js +10 -2
- package/dist/config/cli-commands.d.ts +2 -0
- package/dist/config/cli-commands.js +16 -4
- package/dist/config/load.d.ts +5 -2
- package/dist/config/load.js +23 -12
- 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 +11 -2
- 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/stdin-filter.js +3 -2
- 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/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 +116 -215
- package/dist/ui/BottomStatusRow.d.ts +6 -86
- package/dist/ui/BottomStatusRow.js +11 -26
- package/dist/ui/Composer.d.ts +16 -83
- package/dist/ui/Composer.js +9 -100
- package/dist/ui/Logo.d.ts +3 -5
- package/dist/ui/Logo.js +4 -8
- package/dist/ui/PromptInput.d.ts +4 -1
- package/dist/ui/PromptInput.js +44 -35
- package/dist/ui/QueuePanel.d.ts +8 -0
- package/dist/ui/QueuePanel.js +9 -0
- package/dist/ui/QueueStatusRow.d.ts +2 -0
- package/dist/ui/QueueStatusRow.js +1 -1
- package/dist/ui/StatusBar.d.ts +13 -149
- package/dist/ui/StatusBar.js +27 -142
- package/dist/ui/capabilities.d.ts +2 -0
- package/dist/ui/capabilities.js +6 -0
- 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/color-runtime.d.ts +5 -0
- package/dist/ui/color-runtime.js +29 -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 +4 -0
- package/dist/ui/editor-reducer.js +29 -15
- package/dist/ui/glyphs.js +10 -8
- package/dist/ui/ink-runtime.d.ts +3 -0
- package/dist/ui/ink-runtime.js +30 -0
- 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 +110 -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 +8 -8
- 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/render-cache.d.ts +2 -2
- package/dist/ui/render-cache.js +4 -3
- 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 +1 -0
- package/dist/ui/use-wheel-routing.js +2 -1
- package/package.json +2 -2
package/dist/ui/App.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { modelSettingsResultMessage } from '../agent/model-profile-settings.js';
|
|
3
|
+
import { selectTranscriptEntries, mergePendingEntries } from '../agent/queued-messages.js';
|
|
3
4
|
/**
|
|
4
5
|
* App — the root Ink component. Owns the view reducer, subscribes to the
|
|
5
6
|
* controller's event stream (with a streaming coalescer), wires global
|
|
@@ -7,7 +8,6 @@ import { mergePendingEntries } from '../agent/queued-messages.js';
|
|
|
7
8
|
* input / toast stack / status bar / overlays.
|
|
8
9
|
*/
|
|
9
10
|
import { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react';
|
|
10
|
-
import process from 'node:process';
|
|
11
11
|
import { Text, useApp, useInput, useStdout } from 'ink';
|
|
12
12
|
import { advanceInterruptGesture, cancelInterruptConfirmation, } from '../input/interrupt-gesture.js';
|
|
13
13
|
import { initialViewState, reduceEvent, viewReducer, } from '../agent/reducer.js';
|
|
@@ -19,9 +19,9 @@ import { appendPrompt, loadPromptHistory } from '../config/prompt-history.js';
|
|
|
19
19
|
import { bumpSubmitCount, setMouseNoticeVersion, setVtInputNoticeVersion, } from '../config/ui-state.js';
|
|
20
20
|
import { getLogger } from '../logging/logger.js';
|
|
21
21
|
import { isTerminalStatus } from '../proc/types.js';
|
|
22
|
-
import { getSecrets
|
|
22
|
+
import { getSecrets } from '../logging/secret-registry.js';
|
|
23
23
|
import { redactText } from '../logging/redact.js';
|
|
24
|
-
import {
|
|
24
|
+
import { resolveTuiCapabilities } from './capabilities.js';
|
|
25
25
|
import { useStartupNotices, MOUSE_NOTICE_VERSION, VT_INPUT_NOTICE_VERSION, } from './use-startup-notices.js';
|
|
26
26
|
import { getTheme } from './theme.js';
|
|
27
27
|
import { pickGlyphs } from './glyphs.js';
|
|
@@ -34,7 +34,6 @@ import { StatusBar } from './StatusBar.js';
|
|
|
34
34
|
import { secondsLeft } from '../agent/retry-view.js';
|
|
35
35
|
import { TeamPanel } from './TeamPanel.js';
|
|
36
36
|
import { TodoPanel } from './TodoPanel.js';
|
|
37
|
-
import { ActivityLine, liveSpinner } from './ActivityLine.js';
|
|
38
37
|
import { BottomStatusRow } from './BottomStatusRow.js';
|
|
39
38
|
import { UpdateLine } from './UpdateLine.js';
|
|
40
39
|
// The updater's two PURE modules. `shouldRenderUpdateLine` decides the row's
|
|
@@ -62,7 +61,7 @@ import { publishExitSnapshot } from './exit-snapshot.js';
|
|
|
62
61
|
import { OverlayFrame } from './layout/OverlayFrame.js';
|
|
63
62
|
import { helpRows } from './overlays/HelpOverlay.js';
|
|
64
63
|
import { ModelPicker } from './overlays/ModelPicker.js';
|
|
65
|
-
import { SettingsScreen, compactionSettingsFrom, fastSettingsFrom,
|
|
64
|
+
import { SettingsScreen, compactionSettingsFrom, fastSettingsFrom, } from './overlays/SettingsScreen.js';
|
|
66
65
|
import { ConfirmDialog } from './overlays/ConfirmDialog.js';
|
|
67
66
|
import { QuestionOverlay } from './overlays/QuestionOverlay.js';
|
|
68
67
|
import { PlanReviewOverlay } from './overlays/PlanReviewOverlay.js';
|
|
@@ -70,7 +69,11 @@ import { CommandRegistry, runSlashInput } from '../commands/registry.js';
|
|
|
70
69
|
import { registerBuiltinCommands } from '../commands/builtins.js';
|
|
71
70
|
import { makeSkillsCommand, registerSkillCommands } from '../commands/skills.js';
|
|
72
71
|
import { MODE_LABEL, MODE_TOGGLE_KEYS, nextMode } from '../agent/agent-mode.js';
|
|
73
|
-
import {
|
|
72
|
+
import { createClipboardCoordinator } from './clipboard-task.js';
|
|
73
|
+
import { QueuePanel } from './QueuePanel.js';
|
|
74
|
+
import { QueueOverlay } from './overlays/QueueOverlay.js';
|
|
75
|
+
import { buildQueueLayout } from './layout/queue-layout.js';
|
|
76
|
+
import { interactionCopy } from './interaction-copy.js';
|
|
74
77
|
import { emptyTailState } from './layout/follow-state.js';
|
|
75
78
|
/**
|
|
76
79
|
* Decide what the `Max tokens` field means, WITHOUT letting a bad value in it
|
|
@@ -196,17 +199,13 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
196
199
|
// closing an overlay cannot leave a stale offset behind, and reset whenever
|
|
197
200
|
// the active overlay changes.
|
|
198
201
|
const [overlayScroll, setOverlayScroll] = useState(0);
|
|
202
|
+
const [completion, setCompletion] = useState('none');
|
|
203
|
+
const [copyState, setCopyState] = useState({ busy: false, cleanupPending: false });
|
|
199
204
|
// Bumped whenever the installed skill set changes. The command registry and
|
|
200
205
|
// the completion list are memoized against it, so a skill installed mid-session
|
|
201
206
|
// gets its `/<name>` command and its autocomplete entry immediately.
|
|
202
207
|
const [skillsNonce, setSkillsNonce] = useState(0);
|
|
203
|
-
const caps = useMemo(() => {
|
|
204
|
-
const detected = detectCapabilities(process.env, stdout);
|
|
205
|
-
return {
|
|
206
|
-
colorLevel: cfg.color === false ? 0 : cfg.colorLevel ?? detected.colorLevel,
|
|
207
|
-
unicode: cfg.unicode ?? detected.unicode,
|
|
208
|
-
};
|
|
209
|
-
}, [cfg.color, cfg.colorLevel, cfg.unicode, stdout]);
|
|
208
|
+
const caps = useMemo(() => resolveTuiCapabilities({}, cfg.color), [cfg.color]);
|
|
210
209
|
const theme = useMemo(() => getTheme(cfg.theme, caps), [cfg.theme, caps]);
|
|
211
210
|
const glyphs = useMemo(() => pickGlyphs(caps), [caps]);
|
|
212
211
|
const reducedMotion = cfg.reducedMotion ?? false;
|
|
@@ -1168,22 +1167,36 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1168
1167
|
// redraw when the frame differ has no cache to repaint from. Both are assigned
|
|
1169
1168
|
// here and cleared on unmount, the same shape `updateBridge` uses.
|
|
1170
1169
|
const selectionBridge = terminal?.selection;
|
|
1170
|
+
const copyCoordinator = useMemo(() => createClipboardCoordinator({
|
|
1171
|
+
clipboard: { write: terminal?.writeForeign },
|
|
1172
|
+
onRequest: () => {
|
|
1173
|
+
ctrlCArmed.current = false;
|
|
1174
|
+
if (ctrlCTimer.current)
|
|
1175
|
+
clearTimeout(ctrlCTimer.current);
|
|
1176
|
+
ctrlCTimer.current = null;
|
|
1177
|
+
},
|
|
1178
|
+
onStateChange: setCopyState,
|
|
1179
|
+
onBusy: ({ cleanupPending }) => toast('info', cleanupPending
|
|
1180
|
+
? '\u590d\u5236\u5931\u8d25\uff0c\u6b63\u5728\u6e05\u7406' : '\u6b63\u5728\u590d\u5236'),
|
|
1181
|
+
onResult: (result, request) => {
|
|
1182
|
+
selectionBridge?.onCopied?.(result, request.lines, request.text.length);
|
|
1183
|
+
if (result.status === 'confirmed') {
|
|
1184
|
+
toast('success', `\u5df2\u590d\u5236 ${request.lines} \u884c`);
|
|
1185
|
+
}
|
|
1186
|
+
else if (result.status === 'sent') {
|
|
1187
|
+
toast('info', '\u5df2\u8bf7\u6c42\u7ec8\u7aef\u590d\u5236\uff0c\u7ed3\u679c\u672a\u786e\u8ba4');
|
|
1188
|
+
}
|
|
1189
|
+
else {
|
|
1190
|
+
const text = `${interactionCopy.copyFailed}\uff1a${interactionCopy.copyReasons[result.reason]}`;
|
|
1191
|
+
toast('warn', text);
|
|
1192
|
+
notify('error', text);
|
|
1193
|
+
}
|
|
1194
|
+
},
|
|
1195
|
+
}), []);
|
|
1196
|
+
useEffect(() => () => copyCoordinator.dispose(), [copyCoordinator]);
|
|
1171
1197
|
useEffect(() => {
|
|
1172
1198
|
if (!selectionBridge)
|
|
1173
1199
|
return undefined;
|
|
1174
|
-
selectionBridge.onCopied = (via, lines, chars) => {
|
|
1175
|
-
// IT NAMES THE MECHANISM RATHER THAN CLAIMING SUCCESS (R-7). Neither OSC 52
|
|
1176
|
-
// nor a spawned `xclip` is detectable: tmux without `set -g set-clipboard
|
|
1177
|
-
// on` swallows the first silently, and the second is best-effort by
|
|
1178
|
-
// construction. A toast that said "copied successfully" would be guessing.
|
|
1179
|
-
const noun = lines === 1 ? 'line' : 'lines';
|
|
1180
|
-
if (via === 'none') {
|
|
1181
|
-
toast('warn', 'Nothing could be copied - no clipboard mechanism is available.');
|
|
1182
|
-
return;
|
|
1183
|
-
}
|
|
1184
|
-
const how = via === 'osc52' ? 'terminal clipboard' : 'system clipboard';
|
|
1185
|
-
toast('success', `Sent ${lines} ${noun} (${chars} chars) to the ${how}.`);
|
|
1186
|
-
};
|
|
1187
1200
|
// I-5 of `frame-differ.ts`: writing escapes here would be a foreign write.
|
|
1188
1201
|
// The nonce is the carrier Ctrl+L already uses (§4.13).
|
|
1189
1202
|
selectionBridge.requestRedraw = () => setRedrawNonce((n) => n + 1);
|
|
@@ -1290,6 +1303,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1290
1303
|
};
|
|
1291
1304
|
const makeCtx = (args) => ({
|
|
1292
1305
|
args,
|
|
1306
|
+
requestCopy: copyCoordinator.requestCopy,
|
|
1293
1307
|
controller,
|
|
1294
1308
|
state: stateRef.current,
|
|
1295
1309
|
dispatch: (action) => {
|
|
@@ -1372,6 +1386,9 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1372
1386
|
* start with `/`, recurse into command dispatch.
|
|
1373
1387
|
*/
|
|
1374
1388
|
const submitMessage = (message, opts = {}) => {
|
|
1389
|
+
if (controller.getConfig().modelSettingsRestartRequired) {
|
|
1390
|
+
return { accepted: false, reason: 'Settings require a restart before sending requests.' };
|
|
1391
|
+
}
|
|
1375
1392
|
if (interactionPhase.current === 'starting') {
|
|
1376
1393
|
return { accepted: false, reason: 'A run is still starting. Press Esc twice to cancel.' };
|
|
1377
1394
|
}
|
|
@@ -1381,13 +1398,21 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1381
1398
|
cancelFollowThrough();
|
|
1382
1399
|
cancelConfirmation();
|
|
1383
1400
|
if (interactionPhase.current === 'running') {
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1401
|
+
try {
|
|
1402
|
+
const queueId = controller.queueUserMessage(message);
|
|
1403
|
+
dispatch({ type: 'steerQueued', queueId, text: message });
|
|
1404
|
+
return { accepted: true };
|
|
1405
|
+
}
|
|
1406
|
+
catch (error) {
|
|
1407
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
1408
|
+
notify('error', reason);
|
|
1409
|
+
return { accepted: false, reason };
|
|
1410
|
+
}
|
|
1387
1411
|
}
|
|
1388
1412
|
resetInterrupt();
|
|
1389
1413
|
const sequence = ++submissionSequence.current;
|
|
1390
1414
|
interactionPhase.current = 'starting';
|
|
1415
|
+
dispatch({ type: 'runStarting' });
|
|
1391
1416
|
// `userInitiated: false` skips TWO things and both are wanted: the
|
|
1392
1417
|
// prompt-history append, and the `submitCount` bump that drives the
|
|
1393
1418
|
// composer's hint fade. An auto-continuation demonstrates nothing about what
|
|
@@ -1401,6 +1426,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1401
1426
|
dispatch({ type: 'submit', text: message });
|
|
1402
1427
|
const pre = controller.preflight();
|
|
1403
1428
|
if (!pre.ok) {
|
|
1429
|
+
dispatch({ type: 'runError' });
|
|
1404
1430
|
notify('error', pre.message ?? 'Configuration error.');
|
|
1405
1431
|
interactionPhase.current = 'idle';
|
|
1406
1432
|
resetInterrupt();
|
|
@@ -1414,6 +1440,8 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1414
1440
|
return;
|
|
1415
1441
|
if (outcome?.status !== 'not-started')
|
|
1416
1442
|
return;
|
|
1443
|
+
if (outcome.reason !== 'cancelled')
|
|
1444
|
+
dispatch({ type: 'runError' });
|
|
1417
1445
|
interactionPhase.current = 'idle';
|
|
1418
1446
|
resetInterrupt();
|
|
1419
1447
|
autoContinuationRef.current = false;
|
|
@@ -1451,91 +1479,24 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1451
1479
|
}
|
|
1452
1480
|
return submitMessage(trimmed.startsWith('//') ? raw.replace('//', '/') : raw);
|
|
1453
1481
|
};
|
|
1454
|
-
const
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
controller.setThinkingLevel(values.thinkingLevel);
|
|
1461
|
-
controller.setMaxTokens(cap.applied);
|
|
1462
|
-
// THE SETTER AND THE PERSIST ARE BOTH REQUIRED, the pair `/todo panel`
|
|
1463
|
-
// documents: persisting alone reports success and changes nothing until the
|
|
1464
|
-
// next launch. `thinkingVisible` lives in `ViewState`, not in the config the
|
|
1465
|
-
// controller holds, so the live half is a dispatch rather than a setter —
|
|
1466
|
-
// and it is a no-op when the row already agrees with the current state.
|
|
1467
|
-
if ((values.showThinking === 'on') !== stateRef.current.thinkingVisible) {
|
|
1482
|
+
const handleProfileSave = (draft) => {
|
|
1483
|
+
const result = controller.saveModelSettings(draft);
|
|
1484
|
+
if (!result.ok)
|
|
1485
|
+
return result;
|
|
1486
|
+
if (draft.patch.showThinking !== undefined
|
|
1487
|
+
&& controller.getConfig().showThinking !== stateRef.current.thinkingVisible) {
|
|
1468
1488
|
dispatch({ type: 'toggleThinking' });
|
|
1469
1489
|
}
|
|
1470
|
-
const key = values.apiKey.trim();
|
|
1471
|
-
if (key.length > 0) {
|
|
1472
|
-
// Registration site 4 (§4.4.3), and the one that matters most: this is how
|
|
1473
|
-
// a key most often enters the process, and it happens long after
|
|
1474
|
-
// `installLogging()` ran. A startup snapshot would miss it entirely, and
|
|
1475
|
-
// the vendor regexes only cover keys that look like a vendor's.
|
|
1476
|
-
registerSecret(key);
|
|
1477
|
-
controller.setApiKey(values.provider, key);
|
|
1478
|
-
}
|
|
1479
|
-
const patch = {
|
|
1480
|
-
provider: values.provider,
|
|
1481
|
-
model: values.model,
|
|
1482
|
-
baseUrl: values.baseUrl.trim() ? values.baseUrl.trim() : null,
|
|
1483
|
-
thinkingLevel: values.thinkingLevel,
|
|
1484
|
-
showThinking: values.showThinking === 'on',
|
|
1485
|
-
// PERSIST ONLY, with no paired live setter — the store is allocated in
|
|
1486
|
-
// `AgentController`'s constructor, so this row takes effect on the next
|
|
1487
|
-
// launch. That is what manual row 8 asks for, and it is why the seed above
|
|
1488
|
-
// reads `cfg` rather than `ViewState`.
|
|
1489
|
-
liveToolOutput: values.liveToolOutput === 'on',
|
|
1490
|
-
// Omitted entirely when the field was unusable, so the previous value
|
|
1491
|
-
// survives instead of being overwritten with a guess.
|
|
1492
|
-
...(cap.persist !== 'skip' ? { maxTokens: cap.persist } : {}),
|
|
1493
|
-
};
|
|
1494
|
-
if (key.length > 0)
|
|
1495
|
-
patch.apiKeys = { [values.provider]: key };
|
|
1496
|
-
// A PARTIAL `log` section, safe only because `updatePersistedConfig` deep-
|
|
1497
|
-
// merges it — a shallow merge here would reset `redactSecrets` every time
|
|
1498
|
-
// someone touched the level.
|
|
1499
|
-
patch.log = { level: values.logLevel };
|
|
1500
|
-
// --- Fast tier (fast-model-tier §4.5) ---------------------------------
|
|
1501
|
-
//
|
|
1502
|
-
// THE SETTER AND THE PERSIST ARE BOTH REQUIRED, and this is the pair `/todo
|
|
1503
|
-
// panel` documents (P1-2): `App` reads `controller.getConfig()` at render
|
|
1504
|
-
// time, so persisting alone reports success and changes nothing until the
|
|
1505
|
-
// next launch, while the setter alone forgets by morning.
|
|
1506
|
-
//
|
|
1507
|
-
// `setFastConfig` also re-resolves the tier, which matters HERE more than
|
|
1508
|
-
// anywhere: this same Enter press may have changed the main provider, the
|
|
1509
|
-
// main model or the API key, and `fast.provider: ''` inherits from all
|
|
1510
|
-
// three (§3.2 rule 8 / RV-3).
|
|
1511
|
-
const fastPatch = readFastSettings(values);
|
|
1512
|
-
controller.setFastConfig(fastPatch);
|
|
1513
|
-
patch.fast = fastPatch;
|
|
1514
|
-
// --- Context compaction (context-auto-compaction §4.5) ------------------
|
|
1515
|
-
//
|
|
1516
|
-
// THE SAME SETTER-AND-PERSIST PAIR, and the same reason. `setCompactionConfig`
|
|
1517
|
-
// additionally re-emits the snapshot, which is what moves the status chip and
|
|
1518
|
-
// the gauge's marks without waiting for the next turn.
|
|
1519
|
-
//
|
|
1520
|
-
// `enabled` here flips the LIVE switch only. Whether the wiring exists at all
|
|
1521
|
-
// was decided at construction and cannot be undone in this session (§3.2), so
|
|
1522
|
-
// a user who turns it ON in a `--no-compaction` session gets the persisted
|
|
1523
|
-
// value for next launch and nothing this session — which is exactly what
|
|
1524
|
-
// `/compact on` reports in words.
|
|
1525
|
-
const compactionPatch = readCompactionSettings(values);
|
|
1526
|
-
controller.setCompactionConfig(compactionPatch);
|
|
1527
|
-
if (compactionPatch.enabled !== undefined) {
|
|
1528
|
-
controller.setCompactionEnabled(compactionPatch.enabled);
|
|
1529
|
-
}
|
|
1530
|
-
patch.compaction = compactionPatch;
|
|
1531
|
-
persistConfig(patch);
|
|
1532
|
-
getLogger().reconfigure({ ...controller.getConfig().log, level: values.logLevel });
|
|
1533
1490
|
dispatch({ type: 'setOverlay', overlay: null });
|
|
1534
|
-
toast(
|
|
1491
|
+
toast(result.restartRequired ? 'warn' : 'success', modelSettingsResultMessage(result));
|
|
1492
|
+
return result;
|
|
1535
1493
|
};
|
|
1536
1494
|
const handleModelSelect = (provider, model) => {
|
|
1537
|
-
controller.
|
|
1538
|
-
|
|
1495
|
+
const result = controller.saveModelSettingsPatch({ provider, model });
|
|
1496
|
+
if (!result.ok) {
|
|
1497
|
+
toast('error', modelSettingsResultMessage(result));
|
|
1498
|
+
return;
|
|
1499
|
+
}
|
|
1539
1500
|
dispatch({ type: 'setOverlay', overlay: null });
|
|
1540
1501
|
toast('info', `Model set to ${provider}:${model}.`);
|
|
1541
1502
|
};
|
|
@@ -1642,6 +1603,10 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1642
1603
|
// (`via === 'none'`) still reports through the shared toast funnel and
|
|
1643
1604
|
// returns: the selection is already consumed, and the NEXT Ctrl+C
|
|
1644
1605
|
// walks the original ladder.
|
|
1606
|
+
if (key.ctrl && (input === 'c' || input === 'C') && copyCoordinator.isBusy()) {
|
|
1607
|
+
void copyCoordinator.requestCopy({ text: '', lines: 0 });
|
|
1608
|
+
return;
|
|
1609
|
+
}
|
|
1645
1610
|
if (key.ctrl &&
|
|
1646
1611
|
(input === 'c' || input === 'C') &&
|
|
1647
1612
|
(selectionController?.hasPendingSelection() ?? false)) {
|
|
@@ -1651,8 +1616,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1651
1616
|
clearTimeout(ctrlCTimer.current);
|
|
1652
1617
|
ctrlCTimer.current = null;
|
|
1653
1618
|
if (payload) {
|
|
1654
|
-
|
|
1655
|
-
selectionBridge?.onCopied?.(via, payload.lines, payload.text.length);
|
|
1619
|
+
void copyCoordinator.requestCopy({ text: payload.text, lines: payload.lines });
|
|
1656
1620
|
}
|
|
1657
1621
|
return; // A consumed copy intent never becomes exit or service stop.
|
|
1658
1622
|
}
|
|
@@ -1699,7 +1663,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1699
1663
|
const overlay = stateRef.current.overlay;
|
|
1700
1664
|
// Modes A only. `model` / `confirm` manage their own keys; registering an
|
|
1701
1665
|
// arrow handler for them would double every keypress (R-12).
|
|
1702
|
-
const controlled = overlay === 'help' || overlay === 'settings' || overlay === 'plan';
|
|
1666
|
+
const controlled = overlay === 'help' || overlay === 'settings' || overlay === 'plan' || overlay === 'queue';
|
|
1703
1667
|
if (key.pageUp) {
|
|
1704
1668
|
if (controlled)
|
|
1705
1669
|
setOverlayScroll((n) => Math.max(0, n - OVERLAY_PAGE));
|
|
@@ -1714,7 +1678,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1714
1678
|
// worth more than scrolling six fields (§4.9). `question` keeps them for
|
|
1715
1679
|
// its option cursor, for the same reason; `plan` has no cursor of its own
|
|
1716
1680
|
// and scrolls a line at a time.
|
|
1717
|
-
if ((overlay === 'help' || overlay === 'plan') && (key.upArrow || key.downArrow)) {
|
|
1681
|
+
if ((overlay === 'help' || overlay === 'plan' || overlay === 'queue') && (key.upArrow || key.downArrow)) {
|
|
1718
1682
|
setOverlayScroll((n) => Math.max(0, n + (key.upArrow ? -1 : 1)));
|
|
1719
1683
|
return;
|
|
1720
1684
|
}
|
|
@@ -1809,6 +1773,8 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1809
1773
|
}
|
|
1810
1774
|
if (key.escape) {
|
|
1811
1775
|
const overlay = stateRef.current.overlay;
|
|
1776
|
+
if (overlay === 'settings')
|
|
1777
|
+
return;
|
|
1812
1778
|
if (overlay) {
|
|
1813
1779
|
cancelConfirmation();
|
|
1814
1780
|
if (overlay === 'confirm' && confirmRef.current) {
|
|
@@ -1843,7 +1809,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1843
1809
|
interruptGesture.current = next.state;
|
|
1844
1810
|
if (next.action === 'hint') {
|
|
1845
1811
|
dispatch({ type: 'pushToast', level: 'info',
|
|
1846
|
-
text: '
|
|
1812
|
+
text: '\u4e2d\u65ad\u5df2\u51c6\u5907' });
|
|
1847
1813
|
return;
|
|
1848
1814
|
}
|
|
1849
1815
|
endReasonRef.current.aborted = true;
|
|
@@ -1864,7 +1830,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1864
1830
|
controller.abort();
|
|
1865
1831
|
if (sequence === submissionSequence.current && interactionPhase.current === 'running') {
|
|
1866
1832
|
dispatch({ type: 'pushToast', level: 'info',
|
|
1867
|
-
text: '
|
|
1833
|
+
text: '\u6b63\u5728\u505c\u6b62' });
|
|
1868
1834
|
}
|
|
1869
1835
|
}, [controller, cancelFollowThrough, resetInterrupt]);
|
|
1870
1836
|
// --- Render ------------------------------------------------------------
|
|
@@ -1875,6 +1841,8 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1875
1841
|
// which is what lets the user's `contextWindow` override reach the gauge at
|
|
1876
1842
|
// all. Nothing else in this render read them.
|
|
1877
1843
|
const overlay = state.overlay;
|
|
1844
|
+
const settingsDraft = useMemo(() => overlay === 'settings'
|
|
1845
|
+
? controller.createModelSettingsDraft() : undefined, [controller, overlay]);
|
|
1878
1846
|
const hasKey = controller.hasApiKey();
|
|
1879
1847
|
const empty = state.entries.length === 0;
|
|
1880
1848
|
// RENDER SCOPE, so the chip and the composer hint agree with what the Ctrl+C
|
|
@@ -1923,24 +1891,28 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1923
1891
|
const nowSec = running || compactionLive ? Math.floor(Date.now() / 1000) : undefined;
|
|
1924
1892
|
const viewportBudget = computeViewportRows(rows, draftRows);
|
|
1925
1893
|
const overlayMaxRows = viewportBudget;
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
const
|
|
1894
|
+
const composerBaseRows = 2 + clampDraftRows(rows, draftRows);
|
|
1895
|
+
const baseRail = buildTodoRailLayout({
|
|
1896
|
+
cols: cols - 1, panelEnabled: cfg.todo.panel, overlayOpen: false,
|
|
1897
|
+
itemCount: state.todos?.items.length ?? 0, viewportBudget,
|
|
1898
|
+
teamRows: 0, popupRows: 0, composerBaseRows,
|
|
1899
|
+
});
|
|
1900
|
+
const queueLayout = useMemo(() => buildQueueLayout({
|
|
1901
|
+
pending: state.pendingSteering, columns: baseRail.contentCols, terminalRows: rows,
|
|
1902
|
+
availableRows: Math.max(0, viewportBudget - composerBaseRows - 1), paused: !running,
|
|
1903
|
+
}), [state.pendingSteering, baseRail.contentCols, rows, viewportBudget, composerBaseRows, running]);
|
|
1933
1904
|
const teamLayout = buildTeamPanelLayout({
|
|
1934
|
-
snapshot: state.team, terminalRows: rows,
|
|
1905
|
+
snapshot: state.team, terminalRows: rows,
|
|
1906
|
+
availableRows: Math.max(0, viewportBudget - composerBaseRows - queueLayout.rows - 1),
|
|
1935
1907
|
});
|
|
1936
1908
|
const railLayout = buildTodoRailLayout({
|
|
1937
1909
|
cols: cols - 1, panelEnabled: cfg.todo.panel, overlayOpen: false,
|
|
1938
1910
|
itemCount: state.todos?.items.length ?? 0, viewportBudget,
|
|
1939
|
-
teamRows: teamLayout.rowCount, popupRows, composerBaseRows,
|
|
1911
|
+
teamRows: teamLayout.rowCount, queueRows: queueLayout.rows, popupRows, composerBaseRows,
|
|
1940
1912
|
});
|
|
1941
|
-
// Transcript and composer share contentCols; fixed chrome and overlays use terminal width.
|
|
1942
1913
|
const { visible: showRail, width: railWidth, rows: railRows, contentCols } = railLayout;
|
|
1943
|
-
const
|
|
1914
|
+
const transcriptEntries = useMemo(() => selectTranscriptEntries(state.entries, state.pendingSteering), [state.entries, state.pendingSteering]);
|
|
1915
|
+
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: {
|
|
1944
1916
|
provider: cfg.provider,
|
|
1945
1917
|
model: cfg.model,
|
|
1946
1918
|
baseUrl: cfg.baseUrl ?? '',
|
|
@@ -1969,7 +1941,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1969
1941
|
// the save handler cannot disagree about what `(inherit)` means.
|
|
1970
1942
|
...fastSettingsFrom(cfg.fast),
|
|
1971
1943
|
...compactionSettingsFrom(cfg.compaction),
|
|
1972
|
-
}, apiKeys: cfg.apiKeys, maxRows: overlayMaxRows, cols: cols, scrollOffset: overlayScroll, onScrollClamp: setOverlayScroll, theme: theme, caps: caps,
|
|
1944
|
+
}, 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 }),
|
|
1973
1945
|
// Read from `cfg` (render scope, re-read every frame), so `/retry off`
|
|
1974
1946
|
// while the screen is open shows the new value on the next paint.
|
|
1975
1947
|
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;
|
|
@@ -1977,64 +1949,11 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1977
1949
|
// The wordmark + getting-started card now live INSIDE the viewport, as its
|
|
1978
1950
|
// first block of content, so they scroll away instead of being yanked out of
|
|
1979
1951
|
// the header the moment the first message lands.
|
|
1980
|
-
const opener = empty ? (_jsx(SessionOpener, { variant: pickOpenerVariant(viewportBudget, contentCols
|
|
1952
|
+
const opener = empty ? (_jsx(SessionOpener, { variant: pickOpenerVariant(viewportBudget, contentCols), version: version, cwd: controller.getCwd(), hasKey: hasKey, viewportRows: viewportBudget, theme: theme, caps: caps })) : null;
|
|
1981
1953
|
const density = cfg.density;
|
|
1982
1954
|
const transcriptWindow = cfg.transcriptWindow ?? DEFAULT_TRANSCRIPT_WINDOW;
|
|
1983
|
-
//
|
|
1984
|
-
|
|
1985
|
-
// THE MOUNT CONDITION, NAMED ONCE AND USED TWICE (D-4). This const both gates
|
|
1986
|
-
// the `<ActivityLine>` element below and feeds the derivation on the next
|
|
1987
|
-
// line, so "the line is up" and "every other site is still" can never
|
|
1988
|
-
// disagree. Two copies of the same boolean expression is exactly how that
|
|
1989
|
-
// invariant becomes false in six months, silently and in only one branch.
|
|
1990
|
-
//
|
|
1991
|
-
// NOT `running` ALONE (D-3). While an overlay owns the screen the activity row
|
|
1992
|
-
// is suppressed, so keying suppression off `running` would leave the frame
|
|
1993
|
-
// with ZERO life signals: the bar shows state, this row shows life. Never
|
|
1994
|
-
// trade one duplicate for one absence.
|
|
1995
|
-
//
|
|
1996
|
-
// THE CLAIM ABOVE WAS ONCE FALSE, AND THAT WAS THE WHOLE OF
|
|
1997
|
-
// `activity-spinner-vanishes-behind-toast`. `BottomStatusRow` — not this const
|
|
1998
|
-
// — decides what the row actually shows, and a toast used to take the row
|
|
1999
|
-
// outright, so for its 2.5 s TTL this said "the line is up" while nothing was
|
|
2000
|
-
// mounted and all seven other sites sat still. The answer was NOT to enumerate
|
|
2001
|
-
// `&& !state.toasts.length` here (the next occupant reopens it, and the seven
|
|
2002
|
-
// sites animating out of phase is the defect this whole feature removed):
|
|
2003
|
-
// `BottomStatusRow` now carries the bare spinner alongside the toast, so the
|
|
2004
|
-
// signal is mounted exactly when this const is true and the claim holds by
|
|
2005
|
-
// construction.
|
|
2006
|
-
const activityVisible = running && !overlayNode;
|
|
2007
|
-
// The FOUR VIEW CONSUMERS (transcript x 2, team panel, todo panel) take this;
|
|
2008
|
-
// the activity line keeps the raw config flag (D-5), because it reads that
|
|
2009
|
-
// flag a second time to gate phrase rotation and the widened value would
|
|
2010
|
-
// freeze the phrase at the first word of every run.
|
|
2011
|
-
//
|
|
2012
|
-
// So below `App` the prop now means "this frame does not animate here" rather
|
|
2013
|
-
// than "the user asked for reduced motion". `cfg.reducedMotion` (`:279`) stays
|
|
2014
|
-
// the sole config source, and `spinner-census.test.ts` pins this wiring —
|
|
2015
|
-
// threading the raw flag into a fifth consumer is the one silent way back to
|
|
2016
|
-
// two spinners (R-2).
|
|
2017
|
-
const viewReducedMotion = reducedMotion || activityVisible;
|
|
2018
|
-
// The run status row (spinner + phrase + queue/interrupt hint) replaces the idle
|
|
2019
|
-
// hint row ABOVE the input while running; it exists only when that hint row
|
|
2020
|
-
// would, so the footer height is identical with and without it. `running`, not
|
|
2021
|
-
// `activityVisible`: an overlay must not flip the Composer between two layouts.
|
|
2022
|
-
const runRowEnabled = running && hintRowEnabled;
|
|
2023
|
-
// The raw `reducedMotion` flag lives here and nowhere else (D-5).
|
|
2024
|
-
const runActivity = {
|
|
2025
|
-
startedAt: runStartedAt.current,
|
|
2026
|
-
elapsedMs,
|
|
2027
|
-
reducedMotion,
|
|
2028
|
-
runningTool,
|
|
2029
|
-
// THE ROW MUST NEVER SAY SOMETHING FALSE (§6.1). Compaction runs BEFORE
|
|
2030
|
-
// `turn_start`, so `status` is `'running'` with no tool in flight - precisely
|
|
2031
|
-
// the window in which the rotating phrase would claim the model is thinking
|
|
2032
|
-
// while it is actually waiting on a 30k-token summarization.
|
|
2033
|
-
compacting: state.compaction?.inFlight === true,
|
|
2034
|
-
};
|
|
2035
|
-
// The fixed bottom row takes over the life signal once the run row scrolls out
|
|
2036
|
-
// of the viewport; exactly one of the two ever animates.
|
|
2037
|
-
const runRowShown = runRowEnabled && activityRowVisible;
|
|
1955
|
+
// The fixed status bar remains visible underneath every overlay and toast.
|
|
1956
|
+
const viewReducedMotion = reducedMotion || running || state.runPhase === 'starting' || compactionLive;
|
|
2038
1957
|
// Assigned during render so `/perf` reads the CURRENT frame (see `perfRef`).
|
|
2039
1958
|
// `mountedEntries` is the same number `render-budget.test.tsx` asserts (AC-9).
|
|
2040
1959
|
perfRef.current = {
|
|
@@ -2071,39 +1990,21 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2071
1990
|
// Both keys always WORK (they share one branch in the input handler); this
|
|
2072
1991
|
// only decides which one the user is taught.
|
|
2073
1992
|
const modeToggleKey = vtInputWarning ? MODE_TOGGLE_KEYS.fallback : MODE_TOGGLE_KEYS.primary;
|
|
2074
|
-
const composer = (_jsx(Composer, { measureRef: composerRef,
|
|
2075
|
-
? { activity: runActivity, live: runRowShown && !overlayNode, rowRef: activityRef }
|
|
2076
|
-
: 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: handleSubmit, onHelp: () => dispatch({ type: 'setOverlay', overlay: 'help' }) }));
|
|
1993
|
+
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' }) }));
|
|
2077
1994
|
const team = state.team ? (_jsx(TeamPanel, { snapshot: state.team, layout: teamLayout, rows: rows, cols: contentCols, reducedMotion: viewReducedMotion, theme: theme, caps: caps })) : null;
|
|
2078
1995
|
const rail = showRail && state.todos ? (_jsx(TodoPanel, { snapshot: state.todos, width: railWidth, rows: railRows, running: running, reducedMotion: viewReducedMotion, theme: theme, caps: caps })) : null;
|
|
2079
|
-
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:
|
|
2080
|
-
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,
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
// prop for truthiness, and a React element is truthy however it
|
|
2092
|
-
// renders - so a component that rendered nothing would give the row
|
|
2093
|
-
// ZERO rows and silently unbalance the frame budget. Same rule the
|
|
2094
|
-
// activity line above follows with `running && !overlayNode`.
|
|
2095
|
-
//
|
|
2096
|
-
// `!overlayNode` for the same reason it appears there: the full-screen
|
|
2097
|
-
// branch replaces the whole viewport with the overlay, and an update
|
|
2098
|
-
// notice under a settings screen is noise attached to a surface it is
|
|
2099
|
-
// not visible on.
|
|
2100
|
-
//
|
|
2101
|
-
// `!running` (cli-auto-update D-2): the update notice stays silent for
|
|
2102
|
-
// the whole run. It used to be guaranteed by the activity line
|
|
2103
|
-
// out-ranking it inside this row; with the activity line moved above
|
|
2104
|
-
// the input, the row can be free during a run, so the silence is stated
|
|
2105
|
-
// here instead of inherited.
|
|
2106
|
-
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.overlay ? undefined : state.pendingSteering, interruptHint: interruptGesture.current.phase === 'stopping'
|
|
1996
|
+
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 })] }));
|
|
1997
|
+
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'
|
|
1998
|
+
: running ? 'running' : 'idle', cols, completion,
|
|
1999
|
+
selectionPending: selectionController?.hasPendingSelection() ?? false,
|
|
2000
|
+
copyInFlight: copyState.busy, copyCleanupPending: copyState.cleanupPending,
|
|
2001
|
+
overlay, services: liveServices, hintsEnabled: cfg.hints, modeToggleKey,
|
|
2002
|
+
interruptHint: interruptGesture.current.phase === 'stopping'
|
|
2003
|
+
? 'Esc force-stop' : interruptGesture.current.phase === 'armed'
|
|
2004
|
+
? 'Esc confirm' : 'Esc x2 interrupt',
|
|
2005
|
+
updateAvailable: !!updateSnapshot && !overlayNode && !running
|
|
2006
|
+
&& shouldRenderUpdateLine(updateSnapshot),
|
|
2007
|
+
}, 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'
|
|
2107
2008
|
? 'Esc force-stop' : 'Esc x2 interrupt', model: cfg.model, provider: cfg.provider, usageTotal: state.usageTotal,
|
|
2108
2009
|
// ONE OBJECT INSTEAD OF FOUR PARALLEL PROPS (§4.3). The four were a
|
|
2109
2010
|
// projection of "many sources of truth" into the props layer, and a
|