@golba98/codexa 1.0.3 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -18
- package/bin/codexa.js +62 -144
- package/package.json +4 -3
- package/src/app.tsx +642 -303
- package/src/commands/handler.ts +7 -18
- package/src/config/buildInfo.ts +2 -2
- package/src/config/layeredConfig.ts +1 -1
- package/src/config/runtimeConfig.ts +1 -1
- package/src/config/settings.ts +1 -1
- package/src/config/trustStore.ts +1 -1
- package/src/core/README.md +52 -0
- package/src/core/agent/loop.ts +282 -0
- package/src/core/agent/protocol.ts +211 -0
- package/src/core/agent/tools.ts +414 -0
- package/src/core/{codexExecArgs.ts → codex/codexExecArgs.ts} +2 -2
- package/src/core/{codexLaunch.ts → codex/codexLaunch.ts} +3 -3
- package/src/core/{codexPrompt.ts → codex/codexPrompt.ts} +3 -3
- package/src/core/debug/modelStateDebug.ts +34 -0
- package/src/core/executables/antigravityExecutable.ts +48 -0
- package/src/core/executables/executableResolver.ts +5 -1
- package/src/core/models/codexModelCapabilities.ts +57 -4
- package/src/core/models/codexModelsCacheSeed.ts +153 -0
- package/src/core/models/providerModelCache.ts +106 -0
- package/src/core/perf/renderDebug.ts +10 -6
- package/src/core/process/CommandRunner.ts +12 -1
- package/src/core/providerLauncher/registry.ts +64 -18
- package/src/core/providerLauncher/types.ts +12 -9
- package/src/core/providerLauncher/workspaceConfig.ts +41 -26
- package/src/core/providerRuntime/anthropic.ts +10 -6
- package/src/core/providerRuntime/antigravity.ts +461 -0
- package/src/core/providerRuntime/claudeCodeDiscovery.ts +724 -446
- package/src/core/providerRuntime/claudeCodeDiscoveryDebug.ts +55 -0
- package/src/core/providerRuntime/contextMetadata.ts +12 -24
- package/src/core/providerRuntime/local.ts +129 -51
- package/src/core/providerRuntime/mistralVibe.ts +663 -0
- package/src/core/providerRuntime/models.ts +40 -15
- package/src/core/providerRuntime/reasoning.ts +9 -6
- package/src/core/providerRuntime/registry.ts +76 -4
- package/src/core/providerRuntime/types.ts +20 -14
- package/src/core/providers/codexSubprocess.ts +2 -2
- package/src/core/providers/types.ts +1 -1
- package/src/core/{attachments.ts → shared/attachments.ts} +27 -4
- package/src/core/{cleanupFastFail.ts → shared/cleanupFastFail.ts} +1 -1
- package/src/core/{hollowResponseFormat.ts → shared/hollowResponseFormat.ts} +1 -1
- package/src/core/terminal/clearFrameBoundary.ts +814 -0
- package/src/core/terminal/frameLock.ts +109 -0
- package/src/core/terminal/inkRenderReset.ts +123 -0
- package/src/core/terminal/terminalControl.ts +22 -0
- package/src/core/terminal/terminalTitle.ts +16 -102
- package/src/core/{channel.ts → version/channel.ts} +1 -1
- package/src/core/{updateCheck.ts → version/updateCheck.ts} +10 -5
- package/src/core/{launchContext.ts → workspace/launchContext.ts} +9 -16
- package/src/core/{planStorage.ts → workspace/planStorage.ts} +2 -2
- package/src/core/{workspaceGuard.ts → workspace/workspaceGuard.ts} +97 -13
- package/src/headless/execRunner.ts +2 -2
- package/src/index.tsx +66 -98
- package/src/session/appSession.ts +10 -7
- package/src/session/chatLifecycle.ts +1 -1
- package/src/session/liveRenderScheduler.ts +1 -1
- package/src/session/types.ts +1 -1
- package/src/test/runtimeTestUtils.ts +90 -0
- package/src/ui/{ActivityBars.tsx → chrome/ActivityBars.tsx} +1 -1
- package/src/ui/{ActivityIndicator.tsx → chrome/ActivityIndicator.tsx} +3 -3
- package/src/ui/{AnimatedStatusText.tsx → chrome/AnimatedStatusText.tsx} +3 -3
- package/src/ui/chrome/AppShell.tsx +672 -0
- package/src/ui/{BottomComposer.tsx → chrome/BottomComposer.tsx} +159 -158
- package/src/ui/{DashCard.tsx → chrome/DashCard.tsx} +2 -2
- package/src/ui/{RunFooter.tsx → chrome/RunFooter.tsx} +3 -3
- package/src/ui/chrome/RuntimeStatusBar.tsx +108 -0
- package/src/ui/{Spinner.tsx → chrome/Spinner.tsx} +1 -1
- package/src/ui/{TopHeader.tsx → chrome/TopHeader.tsx} +54 -38
- package/src/ui/{UpdateAvailableCard.tsx → chrome/UpdateAvailableCard.tsx} +5 -5
- package/src/ui/{commandNormalize.ts → input/commandNormalize.ts} +1 -1
- package/src/ui/{focus.ts → input/focus.ts} +1 -1
- package/src/ui/{inputBuffer.ts → input/inputBuffer.ts} +3 -3
- package/src/ui/layout.ts +298 -24
- package/src/ui/{AttachmentImportPanel.tsx → panels/AttachmentImportPanel.tsx} +3 -3
- package/src/ui/{AuthPanel.tsx → panels/AuthPanel.tsx} +5 -5
- package/src/ui/{BackendPicker.tsx → panels/BackendPicker.tsx} +2 -2
- package/src/ui/{ModePicker.tsx → panels/ModePicker.tsx} +2 -2
- package/src/ui/{ModelPicker.tsx → panels/ModelPicker.tsx} +2 -2
- package/src/ui/{ModelPickerScreen.tsx → panels/ModelPickerScreen.tsx} +224 -42
- package/src/ui/{ModelReasoningPicker.tsx → panels/ModelReasoningPicker.tsx} +5 -5
- package/src/ui/{Panel.tsx → panels/Panel.tsx} +1 -1
- package/src/ui/{PermissionsPanel.tsx → panels/PermissionsPanel.tsx} +3 -3
- package/src/ui/{PlanActionPicker.tsx → panels/PlanActionPicker.tsx} +2 -2
- package/src/ui/{PlanReviewPanel.tsx → panels/PlanReviewPanel.tsx} +5 -5
- package/src/ui/panels/ProviderPicker.tsx +737 -0
- package/src/ui/{ReasoningPicker.tsx → panels/ReasoningPicker.tsx} +3 -3
- package/src/ui/{SelectionPanel.tsx → panels/SelectionPanel.tsx} +5 -1
- package/src/ui/{SettingsPanel.tsx → panels/SettingsPanel.tsx} +4 -4
- package/src/ui/{TextEntryPanel.tsx → panels/TextEntryPanel.tsx} +4 -4
- package/src/ui/{ThemePicker.tsx → panels/ThemePicker.tsx} +2 -2
- package/src/ui/{UpdatePromptPanel.tsx → panels/UpdatePromptPanel.tsx} +17 -23
- package/src/ui/{Markdown.tsx → render/Markdown.tsx} +2 -2
- package/src/ui/{diffRenderer.ts → render/diffRenderer.ts} +1 -1
- package/src/ui/{logoVariants.ts → render/logoVariants.ts} +4 -8
- package/src/ui/{modeDisplay.ts → render/modeDisplay.ts} +2 -2
- package/src/ui/{outputPipeline.ts → render/outputPipeline.ts} +2 -2
- package/src/ui/{runtimeDisplay.ts → render/runtimeDisplay.ts} +23 -10
- package/src/ui/{textLayout.ts → render/textLayout.ts} +15 -4
- package/src/ui/{ActionRequiredBlock.tsx → timeline/ActionRequiredBlock.tsx} +3 -3
- package/src/ui/{AgentBlock.tsx → timeline/AgentBlock.tsx} +10 -10
- package/src/ui/{StaticIntroItem.tsx → timeline/StaticIntroItem.tsx} +2 -2
- package/src/ui/{ThinkingBlock.tsx → timeline/ThinkingBlock.tsx} +4 -4
- package/src/ui/{Timeline.tsx → timeline/Timeline.tsx} +1620 -1471
- package/src/ui/timeline/TranscriptShell.tsx +322 -0
- package/src/ui/{TurnGroup.tsx → timeline/TurnGroup.tsx} +15 -15
- package/src/ui/timeline/layoutListWindow.ts +145 -0
- package/src/ui/{progressEntries.ts → timeline/progressEntries.ts} +3 -3
- package/src/ui/{runActivityView.ts → timeline/runActivityView.ts} +1 -1
- package/src/ui/{timelineMeasure.ts → timeline/timelineMeasure.ts} +206 -134
- package/src/core/codex.ts +0 -124
- package/src/ui/AppShell.tsx +0 -706
- package/src/ui/ProviderPicker.tsx +0 -321
- package/src/ui/StaticTranscriptItem.tsx +0 -56
- /package/src/core/{inputDebug.ts → debug/inputDebug.ts} +0 -0
- /package/src/core/{clipboard.ts → shared/clipboard.ts} +0 -0
- /package/src/core/{githubDiagnostics.ts → shared/githubDiagnostics.ts} +0 -0
- /package/src/core/{projectInstructions.ts → workspace/projectInstructions.ts} +0 -0
- /package/src/core/{workspaceActivity.ts → workspace/workspaceActivity.ts} +0 -0
- /package/src/core/{workspaceRoot.ts → workspace/workspaceRoot.ts} +0 -0
- /package/src/ui/{busyStatusAnimation.ts → chrome/busyStatusAnimation.ts} +0 -0
- /package/src/ui/{slashCommands.ts → input/slashCommands.ts} +0 -0
- /package/src/ui/{terminalAnswerFormat.ts → render/terminalAnswerFormat.ts} +0 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import React, { memo, useEffect, useMemo, useRef } from "react";
|
|
2
|
+
import { Box, Static } from "ink";
|
|
3
|
+
import type { RuntimeSummary } from "../../config/runtimeConfig.js";
|
|
4
|
+
import type { CodexAuthState } from "../../core/auth/codexAuth.js";
|
|
5
|
+
import * as renderDebug from "../../core/perf/renderDebug.js";
|
|
6
|
+
import type { TimelineEvent, UIState } from "../../session/types.js";
|
|
7
|
+
import {
|
|
8
|
+
buildActiveRenderItems,
|
|
9
|
+
buildIntroRenderItem,
|
|
10
|
+
buildStaticRenderItems,
|
|
11
|
+
buildTimelineItems,
|
|
12
|
+
TimelineRowView,
|
|
13
|
+
type TimelineItem,
|
|
14
|
+
} from "./Timeline.js";
|
|
15
|
+
import { getShellHeight, getShellWidth, resolveStartupHeaderMode, type TerminalViewport } from "../layout.js";
|
|
16
|
+
import {
|
|
17
|
+
buildNativeTranscriptParts,
|
|
18
|
+
type NativeTranscriptRowItem,
|
|
19
|
+
type TimelineRow,
|
|
20
|
+
} from "./timelineMeasure.js";
|
|
21
|
+
import { LOGO_COMPACT, LOGO_COMPACT_MIN_COLS, LOGO_LARGE, LOGO_MEDIUM, selectLogoVariant } from "../render/logoVariants.js";
|
|
22
|
+
|
|
23
|
+
type TranscriptStaticItem = NativeTranscriptRowItem & { type: "rows" };
|
|
24
|
+
type StaticRenderItem = TranscriptStaticItem;
|
|
25
|
+
|
|
26
|
+
export interface TranscriptShellProps {
|
|
27
|
+
layout: TerminalViewport;
|
|
28
|
+
authState: CodexAuthState;
|
|
29
|
+
workspaceLabel: string;
|
|
30
|
+
workspaceRoot?: string | null;
|
|
31
|
+
runtimeSummary?: RuntimeSummary | null;
|
|
32
|
+
staticEvents: TimelineEvent[];
|
|
33
|
+
activeEvents: TimelineEvent[];
|
|
34
|
+
uiState: UIState;
|
|
35
|
+
composer: React.ReactNode;
|
|
36
|
+
composerRows?: number;
|
|
37
|
+
verboseMode?: boolean;
|
|
38
|
+
clearCount?: number;
|
|
39
|
+
/**
|
|
40
|
+
* Bumped whenever a width-changing resize forces the terminal's app.tsx-owned
|
|
41
|
+
* clear boundary to physically wipe the screen. Folded into <Static>'s key
|
|
42
|
+
* (not the whole component's) so already-flushed content reprints at the
|
|
43
|
+
* new width — without remounting the composer or anything else.
|
|
44
|
+
*/
|
|
45
|
+
repaintGeneration?: number;
|
|
46
|
+
visible?: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function RowsBlock({ rows }: { rows: TimelineRow[] }) {
|
|
50
|
+
return (
|
|
51
|
+
<Box flexDirection="column">
|
|
52
|
+
{rows.map((row) => (
|
|
53
|
+
<TimelineRowView key={row.key} row={row} />
|
|
54
|
+
))}
|
|
55
|
+
</Box>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isTranscriptEvent(event: TimelineEvent): boolean {
|
|
60
|
+
return event.type === "user" || event.type === "assistant" || event.type === "run" || event.type === "shell";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function isHomeScreenState({
|
|
64
|
+
staticEvents,
|
|
65
|
+
activeEvents,
|
|
66
|
+
uiState,
|
|
67
|
+
}: {
|
|
68
|
+
staticEvents: TimelineEvent[];
|
|
69
|
+
activeEvents: TimelineEvent[];
|
|
70
|
+
uiState: UIState;
|
|
71
|
+
}): boolean {
|
|
72
|
+
return uiState.kind === "IDLE"
|
|
73
|
+
&& !staticEvents.some(isTranscriptEvent)
|
|
74
|
+
&& !activeEvents.some(isTranscriptEvent);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function getLogoVariantName(rows: readonly string[]): "large" | "medium" | "compact" | "wordmark" | "none" {
|
|
78
|
+
if (rows.length === 0) return process.env["CODEXA_NO_ASCII_LOGO"] === "1" ? "none" : "wordmark";
|
|
79
|
+
if (rows === LOGO_LARGE || rows.join("\n") === LOGO_LARGE.join("\n")) return "large";
|
|
80
|
+
if (rows === LOGO_MEDIUM || rows.join("\n") === LOGO_MEDIUM.join("\n")) return "medium";
|
|
81
|
+
if (rows === LOGO_COMPACT || rows.join("\n") === LOGO_COMPACT.join("\n")) return "compact";
|
|
82
|
+
return "wordmark";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function getLogoHiddenReason({
|
|
86
|
+
startupHeaderMode,
|
|
87
|
+
logoVariant,
|
|
88
|
+
width,
|
|
89
|
+
}: {
|
|
90
|
+
startupHeaderMode: ReturnType<typeof resolveStartupHeaderMode>;
|
|
91
|
+
logoVariant: ReturnType<typeof getLogoVariantName>;
|
|
92
|
+
width: number;
|
|
93
|
+
}): string | null {
|
|
94
|
+
if (startupHeaderMode === "tiny") return "terminal-too-small";
|
|
95
|
+
if (process.env["CODEXA_NO_ASCII_LOGO"] === "1") return "CODEXA_NO_ASCII_LOGO";
|
|
96
|
+
if (logoVariant === "wordmark") return `no-ascii-variant-fits-width-${width}`;
|
|
97
|
+
if (logoVariant === "none") return `no-logo-variant-fits-width-${width}`;
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function buildTranscriptItems({
|
|
102
|
+
layout,
|
|
103
|
+
authState,
|
|
104
|
+
workspaceLabel,
|
|
105
|
+
workspaceRoot,
|
|
106
|
+
runtimeSummary,
|
|
107
|
+
staticEvents,
|
|
108
|
+
activeEvents,
|
|
109
|
+
uiState,
|
|
110
|
+
composerRows,
|
|
111
|
+
verboseMode,
|
|
112
|
+
}: Pick<
|
|
113
|
+
TranscriptShellProps,
|
|
114
|
+
| "layout"
|
|
115
|
+
| "authState"
|
|
116
|
+
| "workspaceLabel"
|
|
117
|
+
| "workspaceRoot"
|
|
118
|
+
| "runtimeSummary"
|
|
119
|
+
| "staticEvents"
|
|
120
|
+
| "activeEvents"
|
|
121
|
+
| "uiState"
|
|
122
|
+
| "composerRows"
|
|
123
|
+
| "verboseMode"
|
|
124
|
+
>): { staticItems: TranscriptStaticItem[]; liveRows: TimelineRow[]; startupHeaderMode: ReturnType<typeof resolveStartupHeaderMode> } {
|
|
125
|
+
const staticItems = buildTimelineItems(staticEvents);
|
|
126
|
+
const activeItems = buildTimelineItems(activeEvents);
|
|
127
|
+
const turnIds = [...staticItems, ...activeItems]
|
|
128
|
+
.filter((item): item is Extract<TimelineItem, { type: "turn" }> => item.type === "turn")
|
|
129
|
+
.map((item) => item.turnId);
|
|
130
|
+
const startupHeaderMode = resolveStartupHeaderMode({
|
|
131
|
+
cols: layout.cols,
|
|
132
|
+
rows: layout.rows,
|
|
133
|
+
introRows: 8,
|
|
134
|
+
composerRows: composerRows ?? 5,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const parts = buildNativeTranscriptParts(
|
|
138
|
+
[
|
|
139
|
+
buildIntroRenderItem({
|
|
140
|
+
authState,
|
|
141
|
+
workspaceLabel,
|
|
142
|
+
layout,
|
|
143
|
+
providerLabel: runtimeSummary?.providerLabel ?? null,
|
|
144
|
+
startupHeaderMode,
|
|
145
|
+
}),
|
|
146
|
+
...buildStaticRenderItems(staticItems, turnIds, null, null, null),
|
|
147
|
+
...buildActiveRenderItems(activeItems, turnIds, uiState),
|
|
148
|
+
],
|
|
149
|
+
{
|
|
150
|
+
totalWidth: getShellWidth(layout.cols),
|
|
151
|
+
verboseMode,
|
|
152
|
+
debugLabel: "transcript-shell",
|
|
153
|
+
workspaceRoot,
|
|
154
|
+
},
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
staticItems: parts.staticItems.map((item) => ({ ...item, type: "rows" as const })),
|
|
159
|
+
liveRows: parts.liveRows,
|
|
160
|
+
startupHeaderMode,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function TranscriptShellInner({
|
|
165
|
+
layout,
|
|
166
|
+
authState,
|
|
167
|
+
workspaceLabel,
|
|
168
|
+
workspaceRoot = null,
|
|
169
|
+
runtimeSummary = null,
|
|
170
|
+
staticEvents,
|
|
171
|
+
activeEvents,
|
|
172
|
+
uiState,
|
|
173
|
+
composer,
|
|
174
|
+
composerRows,
|
|
175
|
+
verboseMode = false,
|
|
176
|
+
clearCount = 0,
|
|
177
|
+
visible = true,
|
|
178
|
+
}: TranscriptShellProps) {
|
|
179
|
+
const { staticItems, liveRows, startupHeaderMode } = useMemo(
|
|
180
|
+
() => buildTranscriptItems({
|
|
181
|
+
layout,
|
|
182
|
+
authState,
|
|
183
|
+
workspaceLabel,
|
|
184
|
+
workspaceRoot,
|
|
185
|
+
runtimeSummary,
|
|
186
|
+
staticEvents,
|
|
187
|
+
activeEvents,
|
|
188
|
+
uiState,
|
|
189
|
+
composerRows,
|
|
190
|
+
verboseMode,
|
|
191
|
+
}),
|
|
192
|
+
[activeEvents, authState, composerRows, layout, runtimeSummary, staticEvents, uiState, verboseMode, workspaceLabel, workspaceRoot],
|
|
193
|
+
);
|
|
194
|
+
const homeScreenActive = visible && isHomeScreenState({ staticEvents, activeEvents, uiState });
|
|
195
|
+
const visibleStaticItemsRef = useRef(staticItems);
|
|
196
|
+
|
|
197
|
+
useEffect(() => {
|
|
198
|
+
if (visible) {
|
|
199
|
+
visibleStaticItemsRef.current = staticItems;
|
|
200
|
+
}
|
|
201
|
+
}, [staticItems, visible]);
|
|
202
|
+
|
|
203
|
+
const displayedStaticItems = visible ? staticItems : visibleStaticItemsRef.current;
|
|
204
|
+
|
|
205
|
+
const staticRenderItems = useMemo<StaticRenderItem[]>(() => {
|
|
206
|
+
return displayedStaticItems.map((item) => ({
|
|
207
|
+
...item,
|
|
208
|
+
key: `clear-${clearCount}-${item.key}`,
|
|
209
|
+
}));
|
|
210
|
+
}, [clearCount, displayedStaticItems]);
|
|
211
|
+
|
|
212
|
+
const displayedLiveRows = visible ? liveRows : [];
|
|
213
|
+
const staticRowCount = useMemo(
|
|
214
|
+
() => displayedStaticItems.reduce((rowCount, item) => rowCount + item.rows.length, 0),
|
|
215
|
+
[displayedStaticItems],
|
|
216
|
+
);
|
|
217
|
+
const liveBottomSpacerRows = visible
|
|
218
|
+
? Math.max(0, getShellHeight(layout.rows) - staticRowCount - displayedLiveRows.length - (composerRows ?? 0))
|
|
219
|
+
: 0;
|
|
220
|
+
const spacerRows = useMemo<TimelineRow[]>(
|
|
221
|
+
() => Array.from({ length: liveBottomSpacerRows }, (_, index) => ({
|
|
222
|
+
key: `live-bottom-spacer-${clearCount}-${index}`,
|
|
223
|
+
spans: [{ text: " ".repeat(getShellWidth(layout.cols)) }],
|
|
224
|
+
})),
|
|
225
|
+
[clearCount, layout.cols, liveBottomSpacerRows],
|
|
226
|
+
);
|
|
227
|
+
const shellWidth = getShellWidth(layout.cols);
|
|
228
|
+
const introInnerWidth = Math.max(10, shellWidth - 2);
|
|
229
|
+
const selectedLogoRows = startupHeaderMode === "tiny"
|
|
230
|
+
? []
|
|
231
|
+
: startupHeaderMode === "large"
|
|
232
|
+
? selectLogoVariant(introInnerWidth)
|
|
233
|
+
: introInnerWidth >= LOGO_COMPACT_MIN_COLS ? LOGO_COMPACT : [];
|
|
234
|
+
const selectedLogoVariant = getLogoVariantName(selectedLogoRows);
|
|
235
|
+
const logoHiddenReason = getLogoHiddenReason({
|
|
236
|
+
startupHeaderMode,
|
|
237
|
+
logoVariant: selectedLogoVariant,
|
|
238
|
+
width: introInnerWidth,
|
|
239
|
+
});
|
|
240
|
+
const startupTraceKeyRef = useRef<string | null>(null);
|
|
241
|
+
|
|
242
|
+
useEffect(() => {
|
|
243
|
+
const nextKey = [
|
|
244
|
+
layout.cols,
|
|
245
|
+
layout.rows,
|
|
246
|
+
layout.mode,
|
|
247
|
+
startupHeaderMode,
|
|
248
|
+
homeScreenActive ? "home" : "transcript",
|
|
249
|
+
staticEvents.length,
|
|
250
|
+
activeEvents.length,
|
|
251
|
+
uiState.kind,
|
|
252
|
+
selectedLogoVariant,
|
|
253
|
+
clearCount,
|
|
254
|
+
].join("|");
|
|
255
|
+
if (startupTraceKeyRef.current === nextKey) return;
|
|
256
|
+
startupTraceKeyRef.current = nextKey;
|
|
257
|
+
renderDebug.traceEvent("startup", "homeRender", {
|
|
258
|
+
cols: layout.cols,
|
|
259
|
+
rows: layout.rows,
|
|
260
|
+
layoutMode: layout.mode,
|
|
261
|
+
activeRoot: "TranscriptShell",
|
|
262
|
+
messageCount: [...staticEvents, ...activeEvents].filter(isTranscriptEvent).length,
|
|
263
|
+
staticEventsLength: staticEvents.length,
|
|
264
|
+
activeEventsLength: activeEvents.length,
|
|
265
|
+
uiStateKind: uiState.kind,
|
|
266
|
+
selectedLayoutMode: startupHeaderMode,
|
|
267
|
+
selectedLogoVariant,
|
|
268
|
+
logoBranchSelected: selectedLogoVariant !== "none" && selectedLogoVariant !== "wordmark",
|
|
269
|
+
logoHiddenReason,
|
|
270
|
+
composerCount: visible ? 1 : 0,
|
|
271
|
+
footerCount: visible ? 1 : 0,
|
|
272
|
+
homeScreenRendererUsed: homeScreenActive,
|
|
273
|
+
staticItemCount: staticRenderItems.length,
|
|
274
|
+
liveRowCount: displayedLiveRows.length,
|
|
275
|
+
clearCount,
|
|
276
|
+
});
|
|
277
|
+
}, [
|
|
278
|
+
activeEvents,
|
|
279
|
+
clearCount,
|
|
280
|
+
displayedLiveRows.length,
|
|
281
|
+
homeScreenActive,
|
|
282
|
+
layout.cols,
|
|
283
|
+
layout.mode,
|
|
284
|
+
layout.rows,
|
|
285
|
+
logoHiddenReason,
|
|
286
|
+
selectedLogoVariant,
|
|
287
|
+
startupHeaderMode,
|
|
288
|
+
staticEvents,
|
|
289
|
+
staticRenderItems.length,
|
|
290
|
+
uiState.kind,
|
|
291
|
+
visible,
|
|
292
|
+
]);
|
|
293
|
+
|
|
294
|
+
return (
|
|
295
|
+
<Box flexDirection="column" width="100%" display={visible ? "flex" : "none"}>
|
|
296
|
+
<Static key={`static-${clearCount}`} items={staticRenderItems}>
|
|
297
|
+
{(item) => <RowsBlock key={item.key} rows={item.rows} />}
|
|
298
|
+
</Static>
|
|
299
|
+
|
|
300
|
+
{displayedLiveRows.length > 0 && <RowsBlock rows={displayedLiveRows} />}
|
|
301
|
+
{spacerRows.length > 0 && <RowsBlock rows={spacerRows} />}
|
|
302
|
+
|
|
303
|
+
{visible && composer}
|
|
304
|
+
</Box>
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export const TranscriptShell = memo(function TranscriptShell(props: TranscriptShellProps) {
|
|
309
|
+
// repaintGeneration is folded in here (not just <Static>'s own key) because
|
|
310
|
+
// Ink only reliably re-flushes <Static> content on a genuine fresh mount of
|
|
311
|
+
// the whole subtree — its "capture before delete" escape hatch
|
|
312
|
+
// (reconciler.js's isStaticDirty/onImmediateRender) does not fire the same
|
|
313
|
+
// way when only the inner <Static> node is keyed away and remounted on its
|
|
314
|
+
// own. This does remount the composer too, but only on the (already
|
|
315
|
+
// disruptive) event of a real terminal resize, not during normal typing.
|
|
316
|
+
return (
|
|
317
|
+
<TranscriptShellInner
|
|
318
|
+
key={`clear-${props.clearCount ?? 0}-repaint-${props.repaintGeneration ?? 0}`}
|
|
319
|
+
{...props}
|
|
320
|
+
/>
|
|
321
|
+
);
|
|
322
|
+
});
|
|
@@ -9,30 +9,30 @@ import type {
|
|
|
9
9
|
RunToolActivity,
|
|
10
10
|
UIState,
|
|
11
11
|
UserPromptEvent,
|
|
12
|
-
} from "
|
|
13
|
-
import { getAssistantContent, getResponseSegmentText, getRunPlanText } from "
|
|
14
|
-
import { formatTerminalAnswerInline } from "
|
|
12
|
+
} from "../../session/types.js";
|
|
13
|
+
import { getAssistantContent, getResponseSegmentText, getRunPlanText } from "../../session/types.js";
|
|
14
|
+
import { formatTerminalAnswerInline } from "../render/terminalAnswerFormat.js";
|
|
15
15
|
import { ActionRequiredBlock } from "./ActionRequiredBlock.js";
|
|
16
|
-
import { DashCard } from "
|
|
17
|
-
import { useTheme } from "
|
|
18
|
-
import { sanitizeTerminalOutput } from "
|
|
19
|
-
import { wrapPlainText, wrapCommandText } from "
|
|
16
|
+
import { DashCard } from "../chrome/DashCard.js";
|
|
17
|
+
import { useTheme } from "../theme.js";
|
|
18
|
+
import { sanitizeTerminalOutput } from "../../core/terminal/terminalSanitize.js";
|
|
19
|
+
import { wrapPlainText, wrapCommandText } from "../render/textLayout.js";
|
|
20
20
|
import { selectVisibleRunActivity } from "./runActivityView.js";
|
|
21
|
-
import type { RunFileActivity } from "
|
|
22
|
-
import { RUN_OUTPUT_TRUNCATION_NOTICE } from "
|
|
21
|
+
import type { RunFileActivity } from "../../core/workspace/workspaceActivity.js";
|
|
22
|
+
import { RUN_OUTPUT_TRUNCATION_NOTICE } from "../../session/chatLifecycle.js";
|
|
23
23
|
import { formatProgressBlockBodyLines } from "./progressEntries.js";
|
|
24
|
-
import { getUsableShellWidth, transcriptContentIndent } from "
|
|
25
|
-
import { MemoizedRenderMessage } from "
|
|
24
|
+
import { getUsableShellWidth, transcriptContentIndent } from "../layout.js";
|
|
25
|
+
import { MemoizedRenderMessage } from "../render/Markdown.js";
|
|
26
26
|
import {
|
|
27
27
|
sanitizeOutput,
|
|
28
28
|
sanitizeStreamChunk,
|
|
29
29
|
normalizeOutput,
|
|
30
30
|
classifyOutput,
|
|
31
31
|
formatForBox,
|
|
32
|
-
} from "
|
|
33
|
-
import { normalizeCommand, getFriendlyActionLabel } from "
|
|
34
|
-
import * as renderDebug from "
|
|
35
|
-
import { normalizePlanReviewMarkdown } from "
|
|
32
|
+
} from "../render/outputPipeline.js";
|
|
33
|
+
import { normalizeCommand, getFriendlyActionLabel } from "../input/commandNormalize.js";
|
|
34
|
+
import * as renderDebug from "../../core/perf/renderDebug.js";
|
|
35
|
+
import { normalizePlanReviewMarkdown } from "../../core/workspace/planStorage.js";
|
|
36
36
|
|
|
37
37
|
export type TurnOpacity = "active" | "recent" | "dim";
|
|
38
38
|
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utility for windowing large lists in limited terminal rows.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface ListWindowOptions {
|
|
6
|
+
itemCount: number;
|
|
7
|
+
selectedIndex: number;
|
|
8
|
+
availableRows: number;
|
|
9
|
+
/** Fixed rows consumed by headers, borders, titles, etc. */
|
|
10
|
+
chromeRows: number;
|
|
11
|
+
/** Whether to show "↑ N more" and "↓ N more" indicators. */
|
|
12
|
+
showIndicators?: boolean;
|
|
13
|
+
/** Whether to show "Showing X-Y of N" range line. */
|
|
14
|
+
showRangeLine?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ListWindowResult {
|
|
18
|
+
start: number;
|
|
19
|
+
end: number;
|
|
20
|
+
visibleCount: number;
|
|
21
|
+
showAbove: boolean;
|
|
22
|
+
showBelow: boolean;
|
|
23
|
+
showRange: boolean;
|
|
24
|
+
hiddenAbove: number;
|
|
25
|
+
hiddenBelow: number;
|
|
26
|
+
/** Total rows consumed including chrome and indicators. */
|
|
27
|
+
totalRows: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Calculates a contiguous window of items that fits within available rows,
|
|
32
|
+
* ensuring the selected item is always visible.
|
|
33
|
+
*/
|
|
34
|
+
export function calculateListWindow({
|
|
35
|
+
itemCount,
|
|
36
|
+
selectedIndex,
|
|
37
|
+
availableRows,
|
|
38
|
+
chromeRows,
|
|
39
|
+
showIndicators = true,
|
|
40
|
+
showRangeLine = false,
|
|
41
|
+
}: ListWindowOptions): ListWindowResult {
|
|
42
|
+
if (itemCount === 0) {
|
|
43
|
+
return {
|
|
44
|
+
start: 0,
|
|
45
|
+
end: 0,
|
|
46
|
+
visibleCount: 0,
|
|
47
|
+
showAbove: false,
|
|
48
|
+
showBelow: false,
|
|
49
|
+
showRange: false,
|
|
50
|
+
hiddenAbove: 0,
|
|
51
|
+
hiddenBelow: 0,
|
|
52
|
+
totalRows: chromeRows,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const rangeReserved = showRangeLine ? 1 : 0;
|
|
57
|
+
|
|
58
|
+
// If everything fits including the range line, just show everything.
|
|
59
|
+
if (itemCount + chromeRows + rangeReserved <= availableRows) {
|
|
60
|
+
return {
|
|
61
|
+
start: 0,
|
|
62
|
+
end: itemCount,
|
|
63
|
+
visibleCount: itemCount,
|
|
64
|
+
showAbove: false,
|
|
65
|
+
showBelow: false,
|
|
66
|
+
showRange: showRangeLine,
|
|
67
|
+
hiddenAbove: 0,
|
|
68
|
+
hiddenBelow: 0,
|
|
69
|
+
totalRows: itemCount + chromeRows + rangeReserved,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Otherwise we must window.
|
|
74
|
+
// We reserve space for indicators if requested.
|
|
75
|
+
// We MUST fit within availableRows.
|
|
76
|
+
const indicatorReserved = showIndicators ? 2 : 0;
|
|
77
|
+
const baseReserved = chromeRows + indicatorReserved + rangeReserved;
|
|
78
|
+
|
|
79
|
+
let itemRows = Math.max(1, availableRows - baseReserved);
|
|
80
|
+
let start = 0;
|
|
81
|
+
let end = 0;
|
|
82
|
+
|
|
83
|
+
// Iteratively refine to account for dynamic indicators.
|
|
84
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
85
|
+
const visibleCount = Math.max(1, Math.min(itemCount, itemRows));
|
|
86
|
+
|
|
87
|
+
// Center selected item if possible
|
|
88
|
+
start = Math.max(0, selectedIndex - Math.floor(visibleCount / 2));
|
|
89
|
+
if (start + visibleCount > itemCount) {
|
|
90
|
+
start = Math.max(0, itemCount - visibleCount);
|
|
91
|
+
}
|
|
92
|
+
end = Math.min(itemCount, start + visibleCount);
|
|
93
|
+
|
|
94
|
+
const actualAbove = start > 0;
|
|
95
|
+
const actualBelow = end < itemCount;
|
|
96
|
+
const dynamicReserved = chromeRows
|
|
97
|
+
+ (showRangeLine ? 1 : 0)
|
|
98
|
+
+ (showIndicators && actualAbove ? 1 : 0)
|
|
99
|
+
+ (showIndicators && actualBelow ? 1 : 0);
|
|
100
|
+
|
|
101
|
+
const nextItemRows = Math.max(1, availableRows - dynamicReserved);
|
|
102
|
+
if (nextItemRows === itemRows) break;
|
|
103
|
+
itemRows = nextItemRows;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Final visibility check for selectedIndex
|
|
107
|
+
if (selectedIndex < start) {
|
|
108
|
+
start = selectedIndex;
|
|
109
|
+
end = Math.min(itemCount, start + itemRows);
|
|
110
|
+
} else if (selectedIndex >= end) {
|
|
111
|
+
end = selectedIndex + 1;
|
|
112
|
+
start = Math.max(0, end - itemRows);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Final clamping to ensure we don't exceed availableRows if indicators are shown
|
|
116
|
+
let finalAbove = showIndicators && start > 0;
|
|
117
|
+
let finalBelow = showIndicators && end < itemCount;
|
|
118
|
+
|
|
119
|
+
while (end - start + chromeRows + (showRangeLine ? 1 : 0) + (finalAbove ? 1 : 0) + (finalBelow ? 1 : 0) > availableRows && end - start > 1) {
|
|
120
|
+
// Shrink window from the side further from selectedIndex
|
|
121
|
+
if (selectedIndex - start > end - 1 - selectedIndex) {
|
|
122
|
+
start++;
|
|
123
|
+
} else {
|
|
124
|
+
end--;
|
|
125
|
+
}
|
|
126
|
+
finalAbove = showIndicators && start > 0;
|
|
127
|
+
finalBelow = showIndicators && end < itemCount;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
start,
|
|
132
|
+
end,
|
|
133
|
+
visibleCount: end - start,
|
|
134
|
+
showAbove: finalAbove,
|
|
135
|
+
showBelow: finalBelow,
|
|
136
|
+
showRange: showRangeLine,
|
|
137
|
+
hiddenAbove: start,
|
|
138
|
+
hiddenBelow: itemCount - end,
|
|
139
|
+
totalRows: (end - start)
|
|
140
|
+
+ chromeRows
|
|
141
|
+
+ (showRangeLine ? 1 : 0)
|
|
142
|
+
+ (finalAbove ? 1 : 0)
|
|
143
|
+
+ (finalBelow ? 1 : 0),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { RunProgressBlock, RunProgressEntry, RunProgressSource } from "
|
|
2
|
-
import { sanitizeTerminalOutput } from "
|
|
3
|
-
import { wrapPlainText } from "
|
|
1
|
+
import type { RunProgressBlock, RunProgressEntry, RunProgressSource } from "../../session/types.js";
|
|
2
|
+
import { sanitizeTerminalOutput } from "../../core/terminal/terminalSanitize.js";
|
|
3
|
+
import { wrapPlainText } from "../render/textLayout.js";
|
|
4
4
|
|
|
5
5
|
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
6
6
|
|