@aragon-agent/cli 0.6.29 → 0.6.32
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 +2223 -2103
- package/README.md +2957 -2759
- package/dist/agent/controller.d.ts +46 -0
- package/dist/agent/controller.js +175 -7
- package/dist/agent/job-notices.d.ts +143 -0
- package/dist/agent/job-notices.js +182 -0
- package/dist/agent/project-guidance-prompt.js +20 -20
- package/dist/agent/reducer.d.ts +21 -1
- package/dist/agent/reducer.js +7 -0
- package/dist/agent/system-prompt.d.ts +12 -0
- package/dist/agent/system-prompt.js +2 -0
- package/dist/cli.js +399 -62
- package/dist/commands/builtins.js +27 -8
- package/dist/commands/perf.d.ts +79 -2
- package/dist/commands/perf.js +51 -3
- package/dist/commands/registry.d.ts +6 -0
- package/dist/config/cli-commands.d.ts +27 -0
- package/dist/config/cli-commands.js +54 -1
- package/dist/config/env.js +42 -0
- package/dist/config/load.d.ts +3 -1
- package/dist/config/load.js +61 -1
- package/dist/config/schema.d.ts +100 -0
- package/dist/config/schema.js +43 -0
- package/dist/config/store.js +14 -1
- package/dist/config/ui-state.d.ts +9 -0
- package/dist/config/ui-state.js +9 -0
- package/dist/exec/events.d.ts +29 -3
- package/dist/exec/events.js +3 -12
- package/dist/exec/runner.d.ts +24 -0
- package/dist/exec/runner.js +79 -0
- package/dist/input/cursor-report.d.ts +61 -0
- package/dist/input/cursor-report.js +97 -0
- package/dist/input/stdin-filter.d.ts +22 -0
- package/dist/input/stdin-filter.js +48 -3
- package/dist/proc/limits.d.ts +76 -2
- package/dist/proc/limits.js +83 -2
- package/dist/proc/log-mirror.d.ts +59 -0
- package/dist/proc/log-mirror.js +203 -0
- package/dist/proc/log-ring.d.ts +22 -0
- package/dist/proc/log-ring.js +20 -1
- package/dist/proc/notices.d.ts +57 -0
- package/dist/proc/notices.js +101 -0
- package/dist/proc/progress.d.ts +40 -0
- package/dist/proc/progress.js +135 -0
- package/dist/proc/prompt.d.ts +24 -2
- package/dist/proc/prompt.js +51 -2
- package/dist/proc/supervisor.d.ts +35 -2
- package/dist/proc/supervisor.js +142 -43
- package/dist/proc/types.d.ts +89 -1
- package/dist/proc/wait.d.ts +57 -0
- package/dist/proc/wait.js +133 -0
- package/dist/session/persist.js +3 -1
- package/dist/session/validate-session.js +17 -0
- package/dist/tools/bash-tool.d.ts +12 -1
- package/dist/tools/bash-tool.js +100 -13
- package/dist/tools/index.d.ts +26 -2
- package/dist/tools/index.js +43 -2
- package/dist/tools/job-tools.d.ts +54 -0
- package/dist/tools/job-tools.js +329 -0
- package/dist/tools/proc-tools.d.ts +1 -1
- package/dist/tools/proc-tools.js +49 -17
- package/dist/ui/ActivityLine.js +7 -0
- package/dist/ui/App.d.ts +11 -1
- package/dist/ui/App.js +181 -63
- package/dist/ui/ScrollbackShell.d.ts +54 -0
- package/dist/ui/ScrollbackShell.js +15 -0
- package/dist/ui/StatusBar.d.ts +5 -0
- package/dist/ui/StatusBar.js +1 -0
- package/dist/ui/Transcript.d.ts +14 -0
- package/dist/ui/Transcript.js +14 -2
- package/dist/ui/entries/ServiceCard.d.ts +17 -1
- package/dist/ui/entries/ServiceCard.js +39 -4
- package/dist/ui/frame-differ.d.ts +12 -0
- package/dist/ui/layout/status-layout.d.ts +13 -1
- package/dist/ui/layout/status-layout.js +10 -0
- package/dist/ui/layout/virtual-window.js +14 -1
- package/dist/ui/overlays/HelpOverlay.d.ts +1 -1
- package/dist/ui/overlays/HelpOverlay.js +21 -3
- package/dist/ui/run-status-row.d.ts +12 -0
- package/dist/ui/screen.d.ts +29 -0
- package/dist/ui/screen.js +40 -0
- package/dist/ui/scrollback/LiveTail.d.ts +64 -0
- package/dist/ui/scrollback/LiveTail.js +27 -0
- package/dist/ui/scrollback/band.d.ts +105 -0
- package/dist/ui/scrollback/band.js +109 -0
- package/dist/ui/scrollback/budget.d.ts +72 -0
- package/dist/ui/scrollback/budget.js +81 -0
- package/dist/ui/scrollback/canvas.d.ts +99 -0
- package/dist/ui/scrollback/canvas.js +113 -0
- package/dist/ui/scrollback/cursor-probe.d.ts +71 -0
- package/dist/ui/scrollback/cursor-probe.js +133 -0
- package/dist/ui/scrollback/drain.d.ts +32 -0
- package/dist/ui/scrollback/drain.js +64 -0
- package/dist/ui/scrollback/exit-summary.d.ts +23 -0
- package/dist/ui/scrollback/exit-summary.js +25 -0
- package/dist/ui/scrollback/limits.d.ts +74 -0
- package/dist/ui/scrollback/limits.js +74 -0
- package/dist/ui/scrollback/live-preview.d.ts +26 -0
- package/dist/ui/scrollback/live-preview.js +106 -0
- package/dist/ui/scrollback/renderer.d.ts +38 -0
- package/dist/ui/scrollback/renderer.js +158 -0
- package/dist/ui/scrollback/scrollback-writer.d.ts +162 -0
- package/dist/ui/scrollback/scrollback-writer.js +549 -0
- package/dist/ui/scrollback/settled-boundary.d.ts +42 -0
- package/dist/ui/scrollback/settled-boundary.js +106 -0
- package/dist/ui/scrollback/use-scrollback-view.d.ts +103 -0
- package/dist/ui/scrollback/use-scrollback-view.js +294 -0
- package/dist/ui/scrollback/writer-shapes.d.ts +127 -0
- package/dist/ui/scrollback/writer-shapes.js +159 -0
- package/dist/ui/stdout-frame-writer.js +12 -2
- package/dist/ui/terminal-plan.d.ts +52 -0
- package/dist/ui/terminal-plan.js +49 -0
- package/dist/ui/transcript-text.d.ts +11 -0
- package/dist/ui/transcript-text.js +21 -7
- package/dist/ui/use-startup-notices.d.ts +12 -1
- package/dist/ui/use-startup-notices.js +28 -2
- package/package.json +3 -3
- package/skills/project-indexer/SKILL.md +163 -163
- package/skills/project-indexer/templates/cleancode-overrides.md +46 -46
- package/skills/project-indexer/templates/cleancode-template.md +94 -94
- package/skills/project-indexer/templates/config-template.md +69 -69
- package/skills/project-indexer/templates/index-template.md +129 -129
- package/skills/skill-creator/SKILL.md +96 -96
package/dist/ui/App.js
CHANGED
|
@@ -16,13 +16,13 @@ import { computeCost } from '../agent/usage.js';
|
|
|
16
16
|
import { parseMaxTokensInput, DEFAULT_TRANSCRIPT_RETAIN, DEFAULT_TRANSCRIPT_WINDOW, } from '../config/schema.js';
|
|
17
17
|
import { updatePersistedConfig } from '../config/store.js';
|
|
18
18
|
import { appendPrompt, loadPromptHistory } from '../config/prompt-history.js';
|
|
19
|
-
import { bumpSubmitCount, setMouseNoticeVersion, setVtInputNoticeVersion, } from '../config/ui-state.js';
|
|
19
|
+
import { bumpSubmitCount, setMouseNoticeVersion, setScrollModeNoticeVersion, setVtInputNoticeVersion, } from '../config/ui-state.js';
|
|
20
20
|
import { getLogger } from '../logging/logger.js';
|
|
21
21
|
import { isTerminalStatus } from '../proc/types.js';
|
|
22
22
|
import { getSecrets } from '../logging/secret-registry.js';
|
|
23
23
|
import { redactText } from '../logging/redact.js';
|
|
24
24
|
import { resolveTuiCapabilities } from './capabilities.js';
|
|
25
|
-
import { useStartupNotices, MOUSE_NOTICE_VERSION, VT_INPUT_NOTICE_VERSION, } from './use-startup-notices.js';
|
|
25
|
+
import { useStartupNotices, MOUSE_NOTICE_VERSION, SCROLL_MODE_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';
|
|
28
28
|
import { Header } from './Header.js';
|
|
@@ -38,6 +38,12 @@ import { BottomStatusRow } from './BottomStatusRow.js';
|
|
|
38
38
|
// Only the updater's pure visibility predicate enters the status detail plan.
|
|
39
39
|
import { shouldRenderUpdateLine } from '../update/types.js';
|
|
40
40
|
import { AppShell } from './layout/AppShell.js';
|
|
41
|
+
import { ScrollbackShell } from './ScrollbackShell.js';
|
|
42
|
+
import { useScrollbackView } from './scrollback/use-scrollback-view.js';
|
|
43
|
+
import { buildExitSummary } from './scrollback/exit-summary.js';
|
|
44
|
+
import { LiveTail } from './scrollback/LiveTail.js';
|
|
45
|
+
import { SCROLLBACK_LIMITS } from './scrollback/limits.js';
|
|
46
|
+
import { TODO_LIMITS } from '../todo/limits.js';
|
|
41
47
|
import { ScrollViewport } from './layout/ScrollViewport.js';
|
|
42
48
|
import { buildTodoRailLayout } from './layout/todo-layout.js';
|
|
43
49
|
import { buildTeamPanelLayout } from './layout/team-panel.js';
|
|
@@ -132,7 +138,11 @@ function seedViewState(seed) {
|
|
|
132
138
|
? viewReducer(base, { type: 'restoreEntries', entries: seed.entries })
|
|
133
139
|
: base;
|
|
134
140
|
}
|
|
135
|
-
export function App({ controller, version, initialOverlay, initialPrompt, confirmBridge, humanInputBridge, mouseSource, vtInputWarning, updateBridge, pasteBridge, terminal, initialEntries, session, sessionBridge, sessionNotice, }) {
|
|
141
|
+
export function App({ controller, version, initialOverlay, initialPrompt, confirmBridge, humanInputBridge, mouseSource, vtInputWarning, updateBridge, pasteBridge, terminal, scrollMode = 'app', scrollback, initialEntries, session, sessionBridge, sessionNotice, }) {
|
|
142
|
+
const native = scrollMode === 'native' && scrollback !== undefined;
|
|
143
|
+
/** Set when the native writer swallowed repeated tall frames; cleared by a resize. */
|
|
144
|
+
const [guardTripped, setGuardTripped] = useState(false);
|
|
145
|
+
const scrollbackViewRef = useRef(null);
|
|
136
146
|
const { exit } = useApp();
|
|
137
147
|
const { stdout } = useStdout();
|
|
138
148
|
const size = useTerminalSize();
|
|
@@ -164,8 +174,8 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
164
174
|
* can never step past the window and skip content. The unsafe direction is the
|
|
165
175
|
* one that matters, and it is unreachable.
|
|
166
176
|
*/
|
|
167
|
-
const overlayPage = overlayPageSize(dimensions.viewportRows, cols);
|
|
168
|
-
const tooSmall = dimensions.inactive;
|
|
177
|
+
const overlayPage = overlayPageSize(dimensions.viewportRows + (native ? 1 : 0), cols);
|
|
178
|
+
const tooSmall = dimensions.inactive || (native && guardTripped);
|
|
169
179
|
// --- Render governor (tui-render-performance L4). ------------------------
|
|
170
180
|
//
|
|
171
181
|
// FIRST, BEFORE ANY OTHER WORK IN THIS BODY. The hook stamps
|
|
@@ -1054,6 +1064,8 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1054
1064
|
nodeVersion: vtInputWarning.nodeVersion,
|
|
1055
1065
|
onSeen: () => setVtInputNoticeVersion(VT_INPUT_NOTICE_VERSION),
|
|
1056
1066
|
}
|
|
1067
|
+
: undefined, native
|
|
1068
|
+
? { onSeen: () => setScrollModeNoticeVersion(SCROLL_MODE_NOTICE_VERSION) }
|
|
1057
1069
|
: undefined);
|
|
1058
1070
|
// --- Exit snapshot: the only way state reaches cli.tsx (§4.4). ----------
|
|
1059
1071
|
useEffect(() => {
|
|
@@ -1491,6 +1503,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1491
1503
|
...(sessionBridge?.branch ? { branch: sessionBridge.branch } : {}),
|
|
1492
1504
|
dispatch: (action) => {
|
|
1493
1505
|
if (action.type === 'resetConversation' || action.type === 'restoreEntries') {
|
|
1506
|
+
scrollbackViewRef.current?.notifyReset(action.type === 'resetConversation' ? 'clear' : 'restore');
|
|
1494
1507
|
metrics.current = createRunMetrics();
|
|
1495
1508
|
manualMetrics.current = createRunMetrics();
|
|
1496
1509
|
setScrollResetKey((n) => n + 1);
|
|
@@ -1541,6 +1554,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1541
1554
|
}
|
|
1542
1555
|
: {}),
|
|
1543
1556
|
...(terminal?.writeForeign ? { writeForeign: terminal.writeForeign } : {}),
|
|
1557
|
+
...(native ? { nativeScroll: true } : {}),
|
|
1544
1558
|
});
|
|
1545
1559
|
/**
|
|
1546
1560
|
* Submitting a prompt no longer touches `config.json` at all — it appends one
|
|
@@ -2042,7 +2056,9 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2042
2056
|
setRedrawNonce((n) => n + 1);
|
|
2043
2057
|
return;
|
|
2044
2058
|
}
|
|
2045
|
-
|
|
2059
|
+
// Native mode has no scrollable viewport: the terminal owns scrolling, so these
|
|
2060
|
+
// keys are left alone. An open overlay is application content and keeps them.
|
|
2061
|
+
if (!native || stateRef.current.overlay) {
|
|
2046
2062
|
if (key.pageUp) {
|
|
2047
2063
|
scrollBy('pageUp');
|
|
2048
2064
|
return;
|
|
@@ -2063,10 +2079,12 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2063
2079
|
if (key.ctrl && (input === 't' || input === 'T')) {
|
|
2064
2080
|
const revealing = !stateRef.current.thinkingVisible;
|
|
2065
2081
|
dispatch({ type: 'toggleThinking' });
|
|
2082
|
+
// Printed history never changes, so say what the toggle actually reaches.
|
|
2083
|
+
const scope = native ? ' Applies to new messages.' : '';
|
|
2066
2084
|
if (!revealing)
|
|
2067
|
-
toast('info',
|
|
2085
|
+
toast('info', `Thinking hidden.${scope}`);
|
|
2068
2086
|
else
|
|
2069
|
-
toast('info',
|
|
2087
|
+
toast('info', `Thinking shown.${scope}`);
|
|
2070
2088
|
return;
|
|
2071
2089
|
}
|
|
2072
2090
|
if (key.ctrl && (input === 'o' || input === 'O')) {
|
|
@@ -2090,7 +2108,13 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2090
2108
|
e.kind === 'team' ||
|
|
2091
2109
|
e.kind === 'compaction' ||
|
|
2092
2110
|
(e.kind === 'service' && !e.terminal));
|
|
2093
|
-
|
|
2111
|
+
// Native mode cannot rewrite what is already printed, so it PRINTS an expanded
|
|
2112
|
+
// copy as a new block instead of toggling the old one (spec 4.7).
|
|
2113
|
+
if (last && native) {
|
|
2114
|
+
if (!scrollbackViewRef.current?.reprintExpanded(last.id))
|
|
2115
|
+
toast('info', 'Nothing to expand.');
|
|
2116
|
+
}
|
|
2117
|
+
else if (last)
|
|
2094
2118
|
dispatch({ type: 'toggleExpand', id: last.id });
|
|
2095
2119
|
// WIDENED FROM `No tool output to expand.`: with three expandable kinds the
|
|
2096
2120
|
// old wording would be wrong in two of the three empty cases.
|
|
@@ -2226,11 +2250,27 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2226
2250
|
// there is the exact cost `tui-render-performance` exists to remove. The
|
|
2227
2251
|
// running tool, when there is one, is within a few entries of the end.
|
|
2228
2252
|
let runningTool;
|
|
2253
|
+
// THE WAIT PROJECTION (long-task-jobs §4.9 / AC-28). Derived here, in the same
|
|
2254
|
+
// reverse loop, because both answers come from the same place: which tool is in
|
|
2255
|
+
// flight, and — when that tool is `job_wait` — which job it is waiting for and
|
|
2256
|
+
// for how long. The timeout is read from the CALL'S OWN ARGUMENTS rather than
|
|
2257
|
+
// from the config, so a `timeoutSeconds` the model chose is what the row says.
|
|
2258
|
+
let wait;
|
|
2229
2259
|
if (running) {
|
|
2230
2260
|
for (let i = state.entries.length - 1; i >= 0; i -= 1) {
|
|
2231
2261
|
const e = state.entries[i];
|
|
2232
2262
|
if (e.kind === 'tool' && e.status === 'running') {
|
|
2233
2263
|
runningTool = e.name;
|
|
2264
|
+
if (e.name === 'job_wait' && e.args) {
|
|
2265
|
+
const args = e.args;
|
|
2266
|
+
const requested = typeof args.timeoutSeconds === 'number'
|
|
2267
|
+
? args.timeoutSeconds
|
|
2268
|
+
: cfg.jobs.waitSeconds;
|
|
2269
|
+
wait = {
|
|
2270
|
+
jobId: typeof args.job === 'string' ? args.job : 'job',
|
|
2271
|
+
upToSeconds: Math.max(0, Math.round(requested)),
|
|
2272
|
+
};
|
|
2273
|
+
}
|
|
2234
2274
|
break;
|
|
2235
2275
|
}
|
|
2236
2276
|
}
|
|
@@ -2243,7 +2283,6 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2243
2283
|
const frameBudget = buildFrameBudget({ rows, cols, draftRows,
|
|
2244
2284
|
popupRows: overlay ? 0 : popupRows, statusExpanded });
|
|
2245
2285
|
const viewportBudget = frameBudget.viewportRows;
|
|
2246
|
-
const overlayMaxRows = viewportBudget;
|
|
2247
2286
|
const composerBaseRows = 0;
|
|
2248
2287
|
const baseRail = buildTodoRailLayout({
|
|
2249
2288
|
cols: cols - 1, panelEnabled: cfg.todo.panel, overlayOpen: false,
|
|
@@ -2263,9 +2302,58 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2263
2302
|
itemCount: state.todos?.items.length ?? 0, viewportBudget,
|
|
2264
2303
|
teamRows: teamLayout.rowCount, queueRows: queueLayout.rows, popupRows: 0, composerBaseRows,
|
|
2265
2304
|
});
|
|
2266
|
-
const { visible:
|
|
2305
|
+
const { visible: railVisible, width: railWidth, rows: railRows, contentCols } = railLayout;
|
|
2306
|
+
// The FULL-SCREEN rail only: native mode draws its rail inside the live region's
|
|
2307
|
+
// panel band (`nativeRail` below), because there is no fixed frame to hang a column
|
|
2308
|
+
// on. This name says which of the two it is; the old `showRail` read like a global
|
|
2309
|
+
// switch and that is how native mode ended up with no rail at all.
|
|
2310
|
+
const fullscreenRailVisible = railVisible && !native;
|
|
2267
2311
|
const transcriptEntries = useMemo(() => selectTranscriptEntries(state.entries, state.pendingSteering), [state.entries, state.pendingSteering]);
|
|
2268
|
-
|
|
2312
|
+
// --- Native scroll (tui-native-terminal-scroll). -----------------------------
|
|
2313
|
+
//
|
|
2314
|
+
// ALWAYS CALLED, INERT WHEN `enabled` IS FALSE: hooks cannot be conditional, and
|
|
2315
|
+
// an inert hook is what keeps `scrollMode: 'app'` byte-identical (I-S9).
|
|
2316
|
+
const viewCols = native ? Math.max(1, cols - 1) : cols;
|
|
2317
|
+
const header = (_jsx(Header, { columns: viewCols, statusExpanded: statusExpanded, scrolledLines: scrolledLines, overlayOpen: overlay !== null, cwd: controller.getCwd(), provider: cfg.provider, model: cfg.model, hasKey: hasKey, variant: pickHeaderVariant(cols), theme: theme, caps: caps }));
|
|
2318
|
+
// The wordmark + getting-started card now live INSIDE the viewport, as its
|
|
2319
|
+
// first block of content, so they scroll away instead of being yanked out of
|
|
2320
|
+
// the header the moment the first message lands.
|
|
2321
|
+
const opener = empty ? (_jsx(SessionOpener, { variant: pickOpenerVariant(native ? Math.max(0, rows - 8) : viewportBudget, native ? viewCols : contentCols), version: version, cwd: controller.getCwd(), hasKey: hasKey, viewportRows: native ? Math.max(0, rows - 8) : viewportBudget, theme: theme, caps: caps })) : null;
|
|
2322
|
+
const scrollbackView = useScrollbackView({
|
|
2323
|
+
enabled: native, bridge: scrollback, entries: state.entries, runActive: running,
|
|
2324
|
+
theme, caps, density: cfg.density, thinkingVisible: state.thinkingVisible,
|
|
2325
|
+
reducedMotion, rows, cols, headerNode: header, openerNode: opener,
|
|
2326
|
+
overlayOpen: overlay !== null, draftRows, popupRows: overlay ? 0 : popupRows,
|
|
2327
|
+
statusExpanded, forceInactive: native && guardTripped,
|
|
2328
|
+
// The band's inputs. `teamRowsNatural` is the roster layout computed above, so the
|
|
2329
|
+
// band clamps a number this frame already trusts instead of measuring the roster a
|
|
2330
|
+
// second time (`buildTeamPanelLayout` stays the single authority).
|
|
2331
|
+
itemCount: state.todos?.items.length ?? 0,
|
|
2332
|
+
panelEnabled: cfg.todo.panel,
|
|
2333
|
+
teamRowsNatural: teamLayout.rowCount,
|
|
2334
|
+
});
|
|
2335
|
+
scrollbackViewRef.current = scrollbackView;
|
|
2336
|
+
// The two hooks `cli.tsx` calls from OUTSIDE React: the exit summary (every exit
|
|
2337
|
+
// path, some of which run after this tree is gone, so it reads refs, never state)
|
|
2338
|
+
// and the writer's tall-frame guard. A resize clears the degraded state.
|
|
2339
|
+
useLayoutEffect(() => {
|
|
2340
|
+
if (!native || !scrollback)
|
|
2341
|
+
return undefined;
|
|
2342
|
+
scrollback.getSummaryRows = () => {
|
|
2343
|
+
const live = controller.getConfig();
|
|
2344
|
+
return buildExitSummary({
|
|
2345
|
+
entries: stateRef.current.entries, usageTotal: stateRef.current.usageTotal,
|
|
2346
|
+
provider: live.provider, model: live.model, elapsedMs: Date.now() - startedAt.current,
|
|
2347
|
+
...(identityRef.current ? { sessionId: identityRef.current.id } : {}), glyphs,
|
|
2348
|
+
});
|
|
2349
|
+
};
|
|
2350
|
+
scrollback.onGuardTrip = () => setGuardTripped(true);
|
|
2351
|
+
return () => { scrollback.onGuardTrip = null; };
|
|
2352
|
+
}, [native, scrollback, controller, glyphs]);
|
|
2353
|
+
useEffect(() => { setGuardTripped(false); }, [rows, cols]);
|
|
2354
|
+
const nativeBudget = native ? scrollbackView.budget : null;
|
|
2355
|
+
const overlayMaxRows = nativeBudget ? nativeBudget.overlayRows : viewportBudget;
|
|
2356
|
+
const overlayNode = overlay === 'queue' ? (_jsx(QueueOverlay, { pending: state.pendingSteering, cols: viewCols, 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: viewCols, rows: helpRows(theme, caps, native), scrollOffset: overlayScroll, onScrollClamp: setOverlayScroll, theme: theme, caps: caps })) : overlay === 'model' ? (_jsx(ModelPicker, { isActive: !tooSmall, registry: controller.getModelRegistry(), currentProvider: cfg.provider, currentModel: cfg.model, maxRows: overlayMaxRows, cols: viewCols, theme: theme, caps: caps, onSelect: handleModelSelect })) : overlay === 'settings' ? (_jsx(SettingsScreen, { isActive: !tooSmall, initial: {
|
|
2269
2357
|
provider: cfg.provider,
|
|
2270
2358
|
model: cfg.model,
|
|
2271
2359
|
baseUrl: cfg.baseUrl ?? '',
|
|
@@ -2299,15 +2387,10 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2299
2387
|
// the save handler cannot disagree about what `(inherit)` means.
|
|
2300
2388
|
...fastSettingsFrom(cfg.fast),
|
|
2301
2389
|
...compactionSettingsFrom(cfg.compaction),
|
|
2302
|
-
}, apiKeys: cfg.apiKeys, maxRows: overlayMaxRows, cols:
|
|
2390
|
+
}, apiKeys: cfg.apiKeys, maxRows: overlayMaxRows, cols: viewCols, scrollOffset: overlayScroll, onScrollClamp: setOverlayScroll, theme: theme, caps: caps, draft: settingsDraft, probe: probe, onProfileSave: handleProfileSave, onReload: () => controller.createModelSettingsDraft(), onClose: () => dispatch({ type: 'setOverlay', overlay: null }),
|
|
2303
2391
|
// Read from `cfg` (render scope, re-read every frame), so `/retry off`
|
|
2304
2392
|
// while the screen is open shows the new value on the next paint.
|
|
2305
|
-
retrySummary: cfg.retry.enabled && cfg.retry.maxRetries > 0 ? String(cfg.retry.maxRetries) : 'off' })) : overlay === 'confirm' && confirmState ? (_jsx(ConfirmDialog, { isActive: !tooSmall, state: confirmState, maxRows: overlayMaxRows, cols:
|
|
2306
|
-
const header = (_jsx(Header, { columns: cols, statusExpanded: statusExpanded, scrolledLines: scrolledLines, overlayOpen: overlay !== null, cwd: controller.getCwd(), provider: cfg.provider, model: cfg.model, hasKey: hasKey, variant: pickHeaderVariant(cols), theme: theme, caps: caps }));
|
|
2307
|
-
// The wordmark + getting-started card now live INSIDE the viewport, as its
|
|
2308
|
-
// first block of content, so they scroll away instead of being yanked out of
|
|
2309
|
-
// the header the moment the first message lands.
|
|
2310
|
-
const opener = empty ? (_jsx(SessionOpener, { variant: pickOpenerVariant(viewportBudget, contentCols), version: version, cwd: controller.getCwd(), hasKey: hasKey, viewportRows: viewportBudget, theme: theme, caps: caps })) : null;
|
|
2393
|
+
retrySummary: cfg.retry.enabled && cfg.retry.maxRetries > 0 ? String(cfg.retry.maxRetries) : 'off' })) : overlay === 'confirm' && confirmState ? (_jsx(ConfirmDialog, { isActive: !tooSmall, state: confirmState, maxRows: overlayMaxRows, cols: viewCols, theme: theme, caps: caps, onClose: closeConfirm })) : overlay === 'question' && humanRequest?.kind === 'questions' ? (_jsx(QuestionOverlay, { isActive: !tooSmall, questions: humanRequest.questions, maxRows: overlayMaxRows, cols: viewCols, theme: theme, caps: caps, onSubmit: (answers) => resolveHuman({ kind: 'answers', answers, cancelled: false }) })) : overlay === 'plan' && humanRequest?.kind === 'plan' ? (_jsx(PlanReviewOverlay, { isActive: !tooSmall, plan: humanRequest.plan, maxRows: overlayMaxRows, cols: viewCols, scrollOffset: overlayScroll, onScrollClamp: setOverlayScroll, theme: theme, caps: caps, onVerdict: handlePlanVerdict })) : null;
|
|
2311
2394
|
const density = cfg.density;
|
|
2312
2395
|
const transcriptWindow = cfg.transcriptWindow ?? DEFAULT_TRANSCRIPT_WINDOW;
|
|
2313
2396
|
// The fixed status bar remains visible underneath every overlay and toast.
|
|
@@ -2326,9 +2409,14 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2326
2409
|
heightsMeasured: heights.stats().measured,
|
|
2327
2410
|
heightsEstimated: heights.stats().estimated,
|
|
2328
2411
|
cols: contentCols,
|
|
2329
|
-
mode: 'fullscreen',
|
|
2330
|
-
viewportRows: viewportBudget,
|
|
2412
|
+
mode: native ? 'native' : 'fullscreen',
|
|
2413
|
+
viewportRows: nativeBudget ? nativeBudget.liveRows : viewportBudget,
|
|
2331
2414
|
offset: scrolledLines,
|
|
2415
|
+
// `null` outside native mode, and `/perf` prints `n/a` for it: a band row of
|
|
2416
|
+
// zeroes in full-screen mode would read like a panel that failed to show.
|
|
2417
|
+
band: nativeBudget
|
|
2418
|
+
? { ...nativeBudget.band, contentCols: nativeBudget.contentCols }
|
|
2419
|
+
: null,
|
|
2332
2420
|
// A getter: the bridge's `frameReady` and geometry change outside React renders.
|
|
2333
2421
|
get scrollbar() {
|
|
2334
2422
|
return readScrollbarPerf({
|
|
@@ -2376,9 +2464,12 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2376
2464
|
level: latestToast.level === 'success' ? 'info' : latestToast.level } : undefined,
|
|
2377
2465
|
});
|
|
2378
2466
|
const statusInput = {
|
|
2379
|
-
columns:
|
|
2467
|
+
columns: viewCols, phase: state.runPhase, pendingCount: state.pendingSteering.length,
|
|
2380
2468
|
context: state.context, usageTotal: state.usageTotal, thinkingLevel: cfg.thinkingLevel,
|
|
2381
2469
|
elapsedMs, tokPerSec, speedKnown, activeTool: state.activeTool, runOutcome: state.runOutcome,
|
|
2470
|
+
// The bounded-wait label, when `job_wait` is what is holding the run. Absent
|
|
2471
|
+
// for every other tool, which keeps every existing plan byte-identical.
|
|
2472
|
+
...(wait ? { wait } : {}),
|
|
2382
2473
|
waitingForConfirmation: !!humanRequest || !!confirmState,
|
|
2383
2474
|
stopping: interruptGesture.current.phase === 'stopping',
|
|
2384
2475
|
interruptPhase: interruptGesture.current.phase,
|
|
@@ -2392,7 +2483,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2392
2483
|
...feedbackProjection,
|
|
2393
2484
|
};
|
|
2394
2485
|
const details = planStatusDetail({ status: statusInput, model: cfg.model, provider: cfg.provider,
|
|
2395
|
-
hints: { interactionPhase: interactionPhase.current, cols, completion, overlay,
|
|
2486
|
+
hints: { interactionPhase: interactionPhase.current, cols: viewCols, completion, overlay,
|
|
2396
2487
|
selectionPending: selectionController?.hasPendingSelection() ?? false,
|
|
2397
2488
|
copyInFlight: copyState.busy, copyCleanupPending: copyState.cleanupPending,
|
|
2398
2489
|
hasDraft: hasDraftRef.current, services: liveServices,
|
|
@@ -2401,45 +2492,72 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2401
2492
|
updateAvailable: !!updateSnapshot && shouldRenderUpdateLine(updateSnapshot) } });
|
|
2402
2493
|
const composer = (_jsx(Composer, { deleteDisambiguated: terminal?.deleteDisambiguated ?? false, onCompletionContextChange: setCompletion, cols: frameBudget.composerCols, terminalRows: rows, statusExpanded: statusExpanded, onInteraction: returnToComposer, reducedMotion: cfg.reducedMotion ?? false, onEscape: handleComposerEscape, onEscapeDismiss: cancelConfirmation, isActive: overlay === null && !tooSmall, running: state.status === 'running', history: promptHistory, commands: commandOptions, cwd: controller.getCwd(), showHint: false, submitCount: cfg.submitCount, hintsEnabled: cfg.hints, agentMode: state.agentMode, services: liveServices, modeToggleKey: modeToggleKey, popupMaxRows: popupMaxRows, popupMaxHeight: frameBudget.popupMaxHeight, onPopupRowsChange: onPopupRowsChange, onDraftRows: onDraftRows, onDraftPresence: onDraftPresence, draftClearNonce: draftClearNonce, onNotice: notify, theme: theme, caps: caps, onSubmit: handleSubmit, onHelp: () => dispatch({ type: 'setOverlay', overlay: 'help' }) }));
|
|
2403
2494
|
const team = state.team ? (_jsx(TeamPanel, { snapshot: state.team, layout: teamLayout, rows: rows, cols: contentCols, reducedMotion: viewReducedMotion, theme: theme, caps: caps })) : null;
|
|
2404
|
-
|
|
2495
|
+
// --- Native panel band (release-gate-and-native-panels §6.3/§6.4) -------------
|
|
2496
|
+
//
|
|
2497
|
+
// SEPARATE NODES, NOT THE TWO ABOVE (RV-P1-7). Those carry `cols={contentCols}` for
|
|
2498
|
+
// the full-screen layout, which has to stay byte-identical (I-3); reusing them here
|
|
2499
|
+
// would silently narrow it. Every number below comes from `budget.band`, and the
|
|
2500
|
+
// number that decides whether a panel mounts IS the number it lays out against - a
|
|
2501
|
+
// panel that mounts on one value and lays out against another renders nothing and
|
|
2502
|
+
// reports nothing (TodoPanel's own I-10 / P1-3 note).
|
|
2503
|
+
const nativeBand = nativeBudget ? nativeBudget.band : null;
|
|
2504
|
+
const nativeRail = state.todos && nativeBand && nativeBand.railRows >= TODO_LIMITS.panelMinRows
|
|
2505
|
+
? (_jsx(TodoPanel, { snapshot: state.todos, width: nativeBand.railCols, rows: nativeBand.railRows, running: running, reducedMotion: viewReducedMotion, theme: theme, caps: caps })) : null;
|
|
2506
|
+
// `cols` is the band's left column, not `contentCols`: `TeamPanel.activityBudget` is
|
|
2507
|
+
// continuous in `cols`, so a roster laid out against a column it is not in is exactly
|
|
2508
|
+
// the bug that pushes the elapsed column off an 80-column terminal.
|
|
2509
|
+
const nativeTeam = state.team && nativeBand ? (_jsx(TeamPanel, { snapshot: state.team, layout: teamLayout, rows: rows, cols: nativeBand.previewCols, reducedMotion: viewReducedMotion, theme: theme, caps: caps })) : null;
|
|
2510
|
+
const rail = fullscreenRailVisible && state.todos ? (_jsx(TodoPanel, { snapshot: state.todos, width: railWidth, rows: railRows, running: running, reducedMotion: viewReducedMotion, theme: theme, caps: caps })) : null;
|
|
2405
2511
|
const viewport = (_jsxs(ScrollViewport, { active: overlay === null && !tooSmall, overlay: overlayNode, footer: _jsxs(_Fragment, { children: [team, _jsx(QueuePanel, { layout: queueLayout, theme: theme })] }), rail: rail, scrollbar: terminal?.scrollbar, resetKey: scrollResetKey, intent: scrollIntent, pinToBottomNonce: pinToBottomNonce, onScrolledLinesChange: setScrolledLines, onViewportShiftChange: onViewportShiftChange, onGeometryChange: onGeometryChange, geometryKey: `${rows}:${cols}:${viewportBudget}:${frameBudget.composerSlotRows}:${frameBudget.statusRows}:${contentCols}:${overlay}:${tooSmall}`, tailRowsRef: tailSink, hold: selectionHold, resumeMs: cfg.scrollResumeMs, showScrollIndicator: true, cols: contentCols, theme: theme, caps: caps, children: [opener, _jsx(TranscriptList, { entries: transcriptEntries, expandedToolIds: state.expandedToolIds, thinkingVisible: state.thinkingVisible, reducedMotion: viewReducedMotion, density: density, nowSec: nowSec, theme: theme, caps: caps, windowSize: transcriptWindow, cols: contentCols, heights: heights, mountedSink: mountedCount, tailSink: tailSink })] }));
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
}
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2512
|
+
const detailsNode = _jsx(BottomStatusRow, { plan: details, columns: viewCols, theme: theme });
|
|
2513
|
+
const statusNode = (_jsx(StatusBar, { columns: viewCols, speedKnown: speedKnown, ...feedbackProjection, interruptPhase: interruptGesture.current.phase, pendingSteering: state.pendingSteering, runPhase: state.runPhase, activeTool: state.activeTool, ...(wait ? { wait } : {}), runOutcome: state.runOutcome, reducedMotion: reducedMotion, ...(sessionId ? { sessionId } : {}), waitingForConfirmation: !!humanRequest || !!confirmState, stopping: interruptGesture.current.phase === 'stopping', interruptHint: interruptGesture.current.phase === 'stopping'
|
|
2514
|
+
? 'Esc force-stop' : 'Esc x2 interrupt', model: cfg.model, provider: cfg.provider, usageTotal: state.usageTotal,
|
|
2515
|
+
// ONE OBJECT INSTEAD OF FOUR PARALLEL PROPS (§4.3). The four were a
|
|
2516
|
+
// projection of "many sources of truth" into the props layer, and a
|
|
2517
|
+
// caller could update three of them; a single reading cannot be
|
|
2518
|
+
// partially stale. The window and its `known` flag now travel INSIDE
|
|
2519
|
+
// it, resolved by the meter - which is what makes the user's
|
|
2520
|
+
// `contextWindow` override reach the bar.
|
|
2521
|
+
context: state.context, status: state.status, elapsedMs: elapsedMs, thinkingLevel: cfg.thinkingLevel, tokPerSec: tokPerSec, theme: theme, caps: caps, scrolledLines: scrolledLines, redrawNonce: redrawNonce, agentMode: state.agentMode, pendingAgentMode: state.pendingAgentMode, ecoRung: governor.rung, ...(state.team
|
|
2522
|
+
? {
|
|
2523
|
+
teamActive: {
|
|
2524
|
+
running: state.team.runs.filter((r) => r.phase !== 'queued' && r.phase !== 'done' &&
|
|
2525
|
+
r.phase !== 'failed' && r.phase !== 'aborted').length,
|
|
2526
|
+
total: state.team.runs.length,
|
|
2527
|
+
},
|
|
2528
|
+
}
|
|
2529
|
+
: {}), ...(state.todos && !(native
|
|
2530
|
+
? (nativeBudget?.band.railRows ?? 0) > 0
|
|
2531
|
+
: fullscreenRailVisible)
|
|
2532
|
+
? { todoActive: { done: state.todos.doneCount, total: state.todos.total } }
|
|
2533
|
+
: {}), ...(liveServices > 0 ? { servicesActive: { live: liveServices } } : {}), ...(state.fast?.live ? { fastActive: { inFlight: state.fast.inFlight } } : {}),
|
|
2534
|
+
// Manual compaction starts before its next snapshot is published.
|
|
2535
|
+
compactionActive: { inFlight: statusInput.compacting ?? false }, ...(controller.isCompactionEnabled()
|
|
2536
|
+
? {
|
|
2537
|
+
gaugeMarks: {
|
|
2538
|
+
warn: cfg.compaction.warnThreshold * 100,
|
|
2539
|
+
high: cfg.compaction.threshold * 100,
|
|
2540
|
+
},
|
|
2541
|
+
}
|
|
2542
|
+
: {}), ...(state.retry
|
|
2543
|
+
? {
|
|
2544
|
+
retryActive: {
|
|
2545
|
+
attempt: state.retry.attempt,
|
|
2546
|
+
max: state.retry.maxRetries,
|
|
2547
|
+
// Recomputed at RENDER from the absolute `resumeAt`, which is
|
|
2548
|
+
// what keeps the chip and the transcript card showing the same
|
|
2549
|
+
// number without either of them owning a second clock.
|
|
2550
|
+
secondsLeft: secondsLeft(state.retry.resumeAt, Date.now()),
|
|
2551
|
+
},
|
|
2552
|
+
}
|
|
2553
|
+
: {}) }));
|
|
2554
|
+
if (nativeBudget) {
|
|
2555
|
+
return (_jsx(ScrollbackShell, { budget: nativeBudget, overlay: overlayNode, liveTailLines: scrollbackView.liveTailLines,
|
|
2556
|
+
// Real components for the unfinished tail (I-7); the plain rows above stay as
|
|
2557
|
+
// the fallback for a frame with no preview rows.
|
|
2558
|
+
tail: nativeBudget.band.previewRows > 0
|
|
2559
|
+
? (_jsx(LiveTail, { unsettled: scrollbackView.unsettledEntries, maxEntries: SCROLLBACK_LIMITS.liveTailMaxEntries, rows: nativeBudget.band.previewRows, cols: nativeBudget.previewCols, expandedToolIds: state.expandedToolIds, thinkingVisible: state.thinkingVisible, reducedMotion: viewReducedMotion, density: cfg.density, nowSec: nowSec, theme: theme, caps: caps }))
|
|
2560
|
+
: undefined, team: nativeTeam, rail: nativeRail, composer: composer, status: statusNode, details: detailsNode, placeholder: _jsxs(Text, { wrap: "truncate", color: theme.noticeWarn, children: [glyphs.warn, " Terminal too small - needs at least 40 columns and 12 rows."] }), theme: theme }));
|
|
2561
|
+
}
|
|
2562
|
+
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, viewportRows: frameBudget.viewportRows, composer: composer, composerSlotRows: frameBudget.composerSlotRows, statusRows: frameBudget.statusRows, details: detailsNode, status: statusNode }));
|
|
2445
2563
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ScrollbackShell — the bounded LIVE REGION of native-scroll mode
|
|
3
|
+
* (tui-native-terminal-scroll §4.6).
|
|
4
|
+
*
|
|
5
|
+
* The full-screen `AppShell` fills the whole terminal; this shell is only as tall as
|
|
6
|
+
* `budget.liveRows` (the root box height IS that number: giving it the frame height
|
|
7
|
+
* would make the "small region" a whole screen again, and Ink would then repaint a
|
|
8
|
+
* screenful per frame). Everything settled has already been printed into the
|
|
9
|
+
* terminal's own scrollback above it.
|
|
10
|
+
*
|
|
11
|
+
* Top to bottom: [overlay | panel band][composer][status][details].
|
|
12
|
+
*
|
|
13
|
+
* THE PANEL BAND IS THE PART NATIVE MODE WAS MISSING
|
|
14
|
+
* (release-gate-and-native-panels §6.1). A fixed frame can put a rail beside the
|
|
15
|
+
* transcript; without one the rail and the roster simply had nowhere to go, so the
|
|
16
|
+
* default mode showed neither. The band is a row pool inside the live region:
|
|
17
|
+
*
|
|
18
|
+
* +---------------------------------------------+-----------------+
|
|
19
|
+
* | team roster (height = band.teamRows) | TODO rail |
|
|
20
|
+
* +---------------------------------------------+ (height = |
|
|
21
|
+
* | unfinished tail (height = band.previewRows) | band.railRows) |
|
|
22
|
+
* +---------------------------------------------+-----------------+
|
|
23
|
+
*
|
|
24
|
+
* Every box in it has an explicit `height` and `width` plus `overflow="hidden"`, and
|
|
25
|
+
* that is a correctness requirement rather than tidiness: a box that grows with its
|
|
26
|
+
* content would make the region taller than `budget.liveRows`, and a frame as tall as
|
|
27
|
+
* the terminal makes Ink erase the user's scrollback (I-4).
|
|
28
|
+
*
|
|
29
|
+
* `liveTailLines` STAYS. It is the plain-text fallback for the frames where the real
|
|
30
|
+
* tail is not mounted (and the floor of the measurement path); `tail` wins when both
|
|
31
|
+
* are given, so the two can never be drawn on top of each other.
|
|
32
|
+
*/
|
|
33
|
+
import React from 'react';
|
|
34
|
+
import type { Theme } from './theme.js';
|
|
35
|
+
import type { ScrollbackBudget } from './scrollback/budget.js';
|
|
36
|
+
export interface ScrollbackShellProps {
|
|
37
|
+
budget: ScrollbackBudget;
|
|
38
|
+
/** Shown instead of the band when an overlay is open. */
|
|
39
|
+
overlay: React.ReactNode | null;
|
|
40
|
+
/** Plain preview rows: the fallback when `tail` is absent (and what `/perf` counts). */
|
|
41
|
+
liveTailLines: readonly string[];
|
|
42
|
+
/** Real components for the unfinished tail (`LiveTail`). */
|
|
43
|
+
tail?: React.ReactNode;
|
|
44
|
+
/** The subagent roster, drawn at the top of the band's left column. */
|
|
45
|
+
team?: React.ReactNode;
|
|
46
|
+
/** The TODO rail, drawn as the band's right-hand column. */
|
|
47
|
+
rail?: React.ReactNode;
|
|
48
|
+
composer: React.ReactNode;
|
|
49
|
+
status: React.ReactNode;
|
|
50
|
+
details?: React.ReactNode;
|
|
51
|
+
placeholder: React.ReactNode;
|
|
52
|
+
theme: Theme;
|
|
53
|
+
}
|
|
54
|
+
export declare function ScrollbackShell({ budget, overlay, liveTailLines, tail, team, rail, composer, status, details, placeholder, theme, }: ScrollbackShellProps): React.ReactElement;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
export function ScrollbackShell({ budget, overlay, liveTailLines, tail, team, rail, composer, status, details, placeholder, theme, }) {
|
|
4
|
+
const width = budget.contentCols;
|
|
5
|
+
if (budget.inactive) {
|
|
6
|
+
return (_jsx(Box, { flexDirection: "column", height: budget.liveRows, width: width, overflow: "hidden", flexShrink: 0, children: budget.liveRows > 0 ? placeholder : null }));
|
|
7
|
+
}
|
|
8
|
+
const band = budget.band;
|
|
9
|
+
const tailLines = liveTailLines.slice(-budget.liveTailRows);
|
|
10
|
+
// The overlay and the band are alternatives, not siblings: the band reports
|
|
11
|
+
// `bandRows = remaining` for an overlay frame, so drawing both would double the
|
|
12
|
+
// region's height and take the frame to the terminal height (the `ESC[3J` case).
|
|
13
|
+
const top = band.reason === 'overlay' ? (_jsx(Box, { flexDirection: "column", height: budget.overlayRows, flexShrink: 0, overflow: "hidden", children: overlay })) : band.bandRows > 0 ? (_jsxs(Box, { flexDirection: "row", height: band.bandRows, flexShrink: 0, overflow: "hidden", children: [_jsxs(Box, { flexDirection: "column", width: budget.previewCols, flexShrink: 0, overflow: "hidden", children: [band.teamRows > 0 && (_jsx(Box, { height: band.teamRows, flexShrink: 0, overflow: "hidden", children: team })), band.previewRows > 0 && (_jsx(Box, { flexDirection: "column", height: band.previewRows, flexShrink: 0, justifyContent: "flex-end", overflow: "hidden", children: tail ?? tailLines.map((line, index) => (_jsx(Text, { wrap: "truncate", color: theme.muted, children: line }, index))) }))] }), band.railRows > 0 && (_jsx(Box, { height: band.railRows, width: band.railCols, flexShrink: 0, overflow: "hidden", children: rail }))] })) : null;
|
|
14
|
+
return (_jsxs(Box, { flexDirection: "column", height: budget.liveRows, width: width, overflow: "hidden", flexShrink: 0, children: [top, _jsx(Box, { flexDirection: "column", height: budget.composerSlotRows, justifyContent: "flex-end", flexShrink: 0, overflow: "hidden", children: composer }), _jsx(Box, { height: budget.statusRows > 0 ? 1 : 0, flexShrink: 0, overflow: "hidden", children: status }), budget.statusRows === 2 && (_jsx(Box, { height: 1, flexShrink: 0, overflow: "hidden", children: details }))] }));
|
|
15
|
+
}
|
package/dist/ui/StatusBar.d.ts
CHANGED
|
@@ -19,6 +19,11 @@ export interface StatusBarProps {
|
|
|
19
19
|
name: string;
|
|
20
20
|
toolCallId: string;
|
|
21
21
|
} | null;
|
|
22
|
+
/** A bounded wait in flight; replaces the `Running job_wait` label (§4.9). */
|
|
23
|
+
wait?: {
|
|
24
|
+
jobId: string;
|
|
25
|
+
upToSeconds: number;
|
|
26
|
+
};
|
|
22
27
|
runOutcome?: "none" | "ended" | "interrupted" | "failed";
|
|
23
28
|
reducedMotion?: boolean;
|
|
24
29
|
waitingForConfirmation?: boolean;
|
package/dist/ui/StatusBar.js
CHANGED
|
@@ -27,6 +27,7 @@ export function StatusBar(props) {
|
|
|
27
27
|
context: props.context, usageTotal: props.usageTotal,
|
|
28
28
|
thinkingLevel: props.thinkingLevel, elapsedMs: props.elapsedMs, tokPerSec: props.tokPerSec,
|
|
29
29
|
activeTool: props.activeTool, runOutcome: props.runOutcome, spinner: glyph,
|
|
30
|
+
...(props.wait ? { wait: props.wait } : {}),
|
|
30
31
|
speedKnown: props.speedKnown, feedback: props.feedback, escapeAction: props.escapeAction,
|
|
31
32
|
interruptPhase: props.interruptPhase,
|
|
32
33
|
waitingForConfirmation: props.waitingForConfirmation, stopping: props.stopping,
|
package/dist/ui/Transcript.d.ts
CHANGED
|
@@ -26,6 +26,20 @@ interface TranscriptProps {
|
|
|
26
26
|
theme: Theme;
|
|
27
27
|
caps: TermCapabilities;
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* The value `EntryView` should receive for `nowSec` on THIS entry.
|
|
31
|
+
*
|
|
32
|
+
* `undefined` for everything that is not a running tool, which is what keeps
|
|
33
|
+
* every other card's memo boundary intact — `undefined === undefined` in the
|
|
34
|
+
* comparator, on every frame, forever.
|
|
35
|
+
*
|
|
36
|
+
* EXPORTED for `ui/scrollback/LiveTail.tsx` (release-gate-and-native-panels §6.5), and
|
|
37
|
+
* that is the whole of this feature's change to this file: the live region mounts the
|
|
38
|
+
* same `EntryView` the transcript does, so it must feed it the same per-entry clock.
|
|
39
|
+
* A copy of this rule in the band would be a second answer to "does this card tick?"
|
|
40
|
+
* and the two would disagree the first time a consumer is added.
|
|
41
|
+
*/
|
|
42
|
+
export declare function nowSecFor(entry: Entry, nowSec: number | undefined): number | undefined;
|
|
29
43
|
export interface EntryViewProps {
|
|
30
44
|
entry: Entry;
|
|
31
45
|
/** Previous entry in the list; `undefined` for the first item. */
|
package/dist/ui/Transcript.js
CHANGED
|
@@ -27,8 +27,14 @@ import { advanceTailRows } from './layout/follow-state.js';
|
|
|
27
27
|
* `undefined` for everything that is not a running tool, which is what keeps
|
|
28
28
|
* every other card's memo boundary intact — `undefined === undefined` in the
|
|
29
29
|
* comparator, on every frame, forever.
|
|
30
|
+
*
|
|
31
|
+
* EXPORTED for `ui/scrollback/LiveTail.tsx` (release-gate-and-native-panels §6.5), and
|
|
32
|
+
* that is the whole of this feature's change to this file: the live region mounts the
|
|
33
|
+
* same `EntryView` the transcript does, so it must feed it the same per-entry clock.
|
|
34
|
+
* A copy of this rule in the band would be a second answer to "does this card tick?"
|
|
35
|
+
* and the two would disagree the first time a consumer is added.
|
|
30
36
|
*/
|
|
31
|
-
function nowSecFor(entry, nowSec) {
|
|
37
|
+
export function nowSecFor(entry, nowSec) {
|
|
32
38
|
if (entry.kind === 'tool' && entry.status === 'running')
|
|
33
39
|
return nowSec;
|
|
34
40
|
// THE LIVE COMPACTION CARD IS THE SECOND CONSUMER (hardening W5). It is at most
|
|
@@ -36,6 +42,12 @@ function nowSecFor(entry, nowSec) {
|
|
|
36
42
|
// function protects is unchanged for every other card, forever.
|
|
37
43
|
if (entry.kind === 'compaction' && entry.live)
|
|
38
44
|
return nowSec;
|
|
45
|
+
// THE THIRD CONSUMER IS A LIVE JOB (long-task-jobs D-8), and it is the reason
|
|
46
|
+
// this stays a single 1 Hz clock: a task's elapsed time and its `no output`
|
|
47
|
+
// badge both have to move, and a per-card `setInterval` would be one timer per
|
|
48
|
+
// job. `terminal` entries are excluded so a settled record never re-renders.
|
|
49
|
+
if (entry.kind === 'service' && !entry.terminal)
|
|
50
|
+
return nowSec;
|
|
39
51
|
return undefined;
|
|
40
52
|
}
|
|
41
53
|
function noticeColor(level, theme) {
|
|
@@ -113,7 +125,7 @@ function EntryViewImpl({ entry, prev, expanded, thinkingVisible, reducedMotion,
|
|
|
113
125
|
? Math.max(0, nowSec * 1000 - entry.startedAt)
|
|
114
126
|
: undefined, live: entry.live, expanded: expanded, reducedMotion: reducedMotion, theme: theme, caps: caps }));
|
|
115
127
|
case 'service':
|
|
116
|
-
return frame(serviceGlyph(entry, glyphs), serviceCardColor(entry, theme), _jsx(ServiceCard, { serviceId: entry.serviceId, command: entry.command, status: entry.status, url: entry.url, port: entry.port, exitCode: entry.exitCode, startedAt: entry.startedAt, readyAt: entry.readyAt, endedAt: entry.endedAt, rows: entry.rows, terminal: entry.terminal, killIncomplete: entry.killIncomplete, expanded: expanded, reducedMotion: reducedMotion, theme: theme, caps: caps }));
|
|
128
|
+
return frame(serviceGlyph(entry, glyphs), serviceCardColor(entry, theme), _jsx(ServiceCard, { serviceId: entry.serviceId, jobKind: entry.jobKind, command: entry.command, status: entry.status, url: entry.url, port: entry.port, exitCode: entry.exitCode, startedAt: entry.startedAt, readyAt: entry.readyAt, endedAt: entry.endedAt, rows: entry.rows, terminal: entry.terminal, killIncomplete: entry.killIncomplete, progress: entry.progress, stalled: entry.stalled, lastOutputAt: entry.lastOutputAt, nowSec: nowSec, expanded: expanded, reducedMotion: reducedMotion, theme: theme, caps: caps }));
|
|
117
129
|
case 'notice':
|
|
118
130
|
return frame(noticeSymbol(entry.level, theme), noticeColor(entry.level, theme), _jsx(Text, { color: noticeColor(entry.level, theme), children: entry.text }));
|
|
119
131
|
default:
|
|
@@ -23,9 +23,11 @@ import React from 'react';
|
|
|
23
23
|
import type { Theme } from '../theme.js';
|
|
24
24
|
import type { TermCapabilities } from '../capabilities.js';
|
|
25
25
|
import { pickGlyphs } from '../glyphs.js';
|
|
26
|
-
import type { ServiceStatus } from '../../proc/types.js';
|
|
26
|
+
import type { JobKind, ServiceStatus } from '../../proc/types.js';
|
|
27
27
|
export interface ServiceCardProps {
|
|
28
28
|
serviceId: string;
|
|
29
|
+
/** `service` or `task`: NOUN ONLY, never a rename of the entry kind (I-2). */
|
|
30
|
+
jobKind?: JobKind;
|
|
29
31
|
command: string;
|
|
30
32
|
status: ServiceStatus;
|
|
31
33
|
url?: string;
|
|
@@ -39,6 +41,20 @@ export interface ServiceCardProps {
|
|
|
39
41
|
terminal?: boolean;
|
|
40
42
|
killIncomplete?: boolean;
|
|
41
43
|
expanded?: boolean;
|
|
44
|
+
/** The extracted progress line, one row, already clipped by the supervisor. */
|
|
45
|
+
progress?: string;
|
|
46
|
+
/** Silence past `jobs.stallSeconds` - a muted badge, never an action. */
|
|
47
|
+
stalled?: boolean;
|
|
48
|
+
/** When output last arrived; with `nowSec` it renders `no output 6m02s`. */
|
|
49
|
+
lastOutputAt?: number;
|
|
50
|
+
/**
|
|
51
|
+
* The transcript's 1 Hz clock (D-8).
|
|
52
|
+
*
|
|
53
|
+
* ONE CLOCK FOR EVERYTHING. A per-card `setInterval` would be one timer per job,
|
|
54
|
+
* and the transcript's comparator already includes `nowSec`, so the live elapsed
|
|
55
|
+
* figure and the `no output` figure update for free.
|
|
56
|
+
*/
|
|
57
|
+
nowSec?: number;
|
|
42
58
|
/** Accepted for comparator consistency; this card never animates (D-14). */
|
|
43
59
|
reducedMotion?: boolean;
|
|
44
60
|
theme: Theme;
|