@golba98/codexa 1.0.2 → 1.0.4
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 +396 -100
- package/bin/codexa.js +62 -144
- package/package.json +14 -8
- package/src/app.tsx +596 -306
- package/src/commands/handler.ts +6 -6
- package/src/config/buildInfo.ts +2 -2
- package/src/config/layeredConfig.ts +1 -1
- package/src/config/persistence.ts +10 -0
- package/src/config/runtimeConfig.ts +1 -1
- package/src/config/settings.ts +8 -16
- package/src/config/trustStore.ts +1 -1
- package/src/config/updateCheckCache.ts +19 -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/codexExecutable.ts +1 -0
- package/src/core/executables/executableResolver.ts +65 -43
- package/src/core/perf/renderDebug.ts +10 -6
- package/src/core/process/processValidation.ts +9 -5
- package/src/core/providerLauncher/launcher.ts +59 -42
- package/src/core/providerLauncher/registry.ts +30 -14
- package/src/core/providerLauncher/types.ts +11 -9
- package/src/core/providerLauncher/workspaceConfig.ts +41 -26
- package/src/core/providerRuntime/anthropic.ts +7 -1
- package/src/core/providerRuntime/antigravity.ts +305 -0
- package/src/core/providerRuntime/claudeCodeDiscovery.ts +268 -22
- 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/models.ts +22 -11
- package/src/core/providerRuntime/registry.ts +58 -31
- package/src/core/providerRuntime/types.ts +19 -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/version/channel.ts +23 -0
- package/src/core/version/updateCheck.ts +193 -0
- package/src/core/{launchContext.ts → workspace/launchContext.ts} +32 -39
- 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 +43 -89
- 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 +3 -2
- package/src/ui/ActionRequiredBlock.tsx +5 -5
- package/src/ui/ActivityBars.tsx +3 -3
- package/src/ui/ActivityIndicator.tsx +6 -6
- package/src/ui/AgentBlock.tsx +6 -6
- package/src/ui/AnimatedStatusText.tsx +1 -1
- package/src/ui/AppShell.tsx +670 -719
- package/src/ui/AttachmentImportPanel.tsx +8 -8
- package/src/ui/AuthPanel.tsx +20 -20
- package/src/ui/BottomComposer.tsx +158 -118
- package/src/ui/DashCard.tsx +3 -3
- package/src/ui/Markdown.tsx +17 -17
- package/src/ui/ModelPickerScreen.tsx +222 -42
- package/src/ui/ModelReasoningPicker.tsx +15 -15
- package/src/ui/Panel.tsx +3 -3
- package/src/ui/PlanActionPicker.tsx +6 -6
- package/src/ui/PlanReviewPanel.tsx +9 -9
- package/src/ui/ProviderPicker.tsx +735 -321
- package/src/ui/RunFooter.tsx +3 -3
- package/src/ui/RuntimeStatusBar.tsx +108 -0
- package/src/ui/SelectionPanel.tsx +8 -4
- package/src/ui/SettingsPanel.tsx +9 -9
- package/src/ui/Spinner.tsx +1 -1
- package/src/ui/TextEntryPanel.tsx +11 -11
- package/src/ui/ThinkingBlock.tsx +8 -8
- package/src/ui/Timeline.tsx +1625 -1472
- package/src/ui/TopHeader.tsx +437 -293
- package/src/ui/TranscriptShell.tsx +322 -0
- package/src/ui/TurnGroup.tsx +33 -33
- package/src/ui/UpdateAvailableCard.tsx +41 -0
- package/src/ui/UpdatePromptPanel.tsx +197 -0
- package/src/ui/focus.ts +3 -0
- package/src/ui/layout.ts +299 -25
- package/src/ui/layoutListWindow.ts +145 -0
- package/src/ui/logoVariants.ts +103 -0
- package/src/ui/modeDisplay.ts +12 -12
- package/src/ui/runtimeDisplay.ts +112 -0
- package/src/ui/textLayout.ts +15 -4
- package/src/ui/theme.tsx +274 -395
- package/src/ui/timelineMeasure.ts +218 -136
- package/scripts/audit-codexa-capabilities.mjs +0 -466
- package/scripts/gen-build-info.mjs +0 -33
- package/scripts/smoke-terminal-bench.mjs +0 -35
- package/src/appRenderStability.test.ts +0 -131
- package/src/commands/handler.test.ts +0 -655
- package/src/config/launchArgs.test.ts +0 -189
- package/src/config/layeredConfig.test.ts +0 -143
- package/src/config/persistence.test.ts +0 -114
- package/src/config/runtimeConfig.test.ts +0 -218
- package/src/config/settings.test.ts +0 -155
- package/src/config/trustStore.test.ts +0 -29
- package/src/core/attachments.test.ts +0 -155
- package/src/core/auth/codexAuth.test.ts +0 -68
- package/src/core/cleanupFastFail.test.ts +0 -76
- package/src/core/codex.ts +0 -124
- package/src/core/codexExecArgs.test.ts +0 -195
- package/src/core/codexLaunch.test.ts +0 -205
- package/src/core/codexPrompt.test.ts +0 -252
- package/src/core/executables/codexExecutable.test.ts +0 -212
- package/src/core/executables/executableResolver.test.ts +0 -129
- package/src/core/executables/geminiExecutable.test.ts +0 -116
- package/src/core/executables/pathSanityScan.test.ts +0 -47
- package/src/core/githubDiagnostics.test.ts +0 -92
- package/src/core/hollowResponseFormat.test.ts +0 -58
- package/src/core/launchContext.test.ts +0 -157
- package/src/core/models/codexCapabilities.test.ts +0 -45
- package/src/core/models/codexModelCapabilities.test.ts +0 -246
- package/src/core/models/modelSpecs.test.ts +0 -283
- package/src/core/perf/renderDebug.test.ts +0 -230
- package/src/core/planStorage.test.ts +0 -143
- package/src/core/process/CommandRunner.test.ts +0 -105
- package/src/core/projectInstructions.test.ts +0 -50
- package/src/core/providerLauncher/launcher.test.ts +0 -238
- package/src/core/providerLauncher/registry.test.ts +0 -324
- package/src/core/providerLauncher/workspaceConfig.test.ts +0 -638
- package/src/core/providerRuntime/anthropic.test.ts +0 -1120
- package/src/core/providerRuntime/capabilityProfile.test.ts +0 -311
- package/src/core/providerRuntime/contextMetadata.test.ts +0 -468
- package/src/core/providerRuntime/gemini.test.ts +0 -437
- package/src/core/providerRuntime/lmstudio.test.ts +0 -168
- package/src/core/providerRuntime/local.test.ts +0 -787
- package/src/core/providerRuntime/registry.test.ts +0 -233
- package/src/core/providers/codexJsonStream.test.ts +0 -148
- package/src/core/providers/codexSubprocess.test.ts +0 -68
- package/src/core/providers/codexTranscript.test.ts +0 -284
- package/src/core/terminal/startupClear.test.ts +0 -55
- package/src/core/terminal/terminalCapabilities.test.ts +0 -93
- package/src/core/terminal/terminalControl.test.ts +0 -75
- package/src/core/terminal/terminalSanitize.test.ts +0 -22
- package/src/core/terminal/terminalSelection.test.ts +0 -42
- package/src/core/terminal/terminalTitle.test.ts +0 -328
- package/src/core/updateCheck.test.ts +0 -194
- package/src/core/updateCheck.ts +0 -172
- package/src/core/workspaceActivity.test.ts +0 -163
- package/src/core/workspaceGuard.test.ts +0 -151
- package/src/core/workspaceRoot.test.ts +0 -23
- package/src/exec.test.ts +0 -13
- package/src/headless/execArgs.test.ts +0 -147
- package/src/headless/execRunner.test.ts +0 -436
- package/src/index.test.tsx +0 -620
- package/src/session/appSession.test.ts +0 -897
- package/src/session/chatLifecycle.test.ts +0 -64
- package/src/session/liveRenderScheduler.test.ts +0 -201
- package/src/session/planFlow.test.ts +0 -103
- package/src/session/planTranscript.test.ts +0 -65
- package/src/session/promptRunSchedule.test.ts +0 -36
- package/src/ui/ActivityIndicator.test.tsx +0 -58
- package/src/ui/AgentBlock.test.ts +0 -6
- package/src/ui/AnimatedStatusText.test.ts +0 -16
- package/src/ui/AppShell.test.tsx +0 -1776
- package/src/ui/AttachmentImportPanel.test.tsx +0 -204
- package/src/ui/BottomComposer.test.ts +0 -674
- package/src/ui/CodexLogo.tsx +0 -55
- package/src/ui/Markdown.test.ts +0 -157
- package/src/ui/ModelPickerProviderScope.test.tsx +0 -411
- package/src/ui/ModelPickerScreen.test.tsx +0 -99
- package/src/ui/ModelPickerState.test.tsx +0 -151
- package/src/ui/ModelReasoningPicker.test.tsx +0 -447
- package/src/ui/PlanReviewPanel.test.tsx +0 -267
- package/src/ui/PromptCardBorder.test.tsx +0 -161
- package/src/ui/ProviderPicker.test.tsx +0 -289
- package/src/ui/ProviderShortcut.test.tsx +0 -143
- package/src/ui/SettingsPanel.test.tsx +0 -233
- package/src/ui/StaticTranscriptItem.tsx +0 -56
- package/src/ui/Timeline.test.ts +0 -2067
- package/src/ui/TimelineNavigation.test.tsx +0 -201
- package/src/ui/TopHeader.test.tsx +0 -254
- package/src/ui/TurnGroup.test.tsx +0 -365
- package/src/ui/busyStatusAnimation.test.ts +0 -30
- package/src/ui/commandNormalize.test.ts +0 -142
- package/src/ui/diffRenderer.test.ts +0 -102
- package/src/ui/focusFlow.test.tsx +0 -1098
- package/src/ui/inputBuffer.test.ts +0 -151
- package/src/ui/layout.test.ts +0 -146
- package/src/ui/modeDisplay.test.ts +0 -42
- package/src/ui/runActivityView.test.ts +0 -89
- package/src/ui/runLifecycleView.test.tsx +0 -237
- package/src/ui/statusRenderIsolation.test.tsx +0 -654
- package/src/ui/terminalAnswerFormat.test.ts +0 -19
- package/src/ui/textLayout.test.ts +0 -18
- package/src/ui/themeFlow.test.ts +0 -53
- package/src/ui/timelineMeasureCache.test.ts +0 -986
- /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
|
@@ -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 "./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
|
+
});
|
package/src/ui/TurnGroup.tsx
CHANGED
|
@@ -18,7 +18,7 @@ import { useTheme } from "./theme.js";
|
|
|
18
18
|
import { sanitizeTerminalOutput } from "../core/terminal/terminalSanitize.js";
|
|
19
19
|
import { wrapPlainText, wrapCommandText } from "./textLayout.js";
|
|
20
20
|
import { selectVisibleRunActivity } from "./runActivityView.js";
|
|
21
|
-
import type { RunFileActivity } from "../core/workspaceActivity.js";
|
|
21
|
+
import type { RunFileActivity } from "../core/workspace/workspaceActivity.js";
|
|
22
22
|
import { RUN_OUTPUT_TRUNCATION_NOTICE } from "../session/chatLifecycle.js";
|
|
23
23
|
import { formatProgressBlockBodyLines } from "./progressEntries.js";
|
|
24
24
|
import { getUsableShellWidth, transcriptContentIndent } from "./layout.js";
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
} from "./outputPipeline.js";
|
|
33
33
|
import { normalizeCommand, getFriendlyActionLabel } from "./commandNormalize.js";
|
|
34
34
|
import * as renderDebug from "../core/perf/renderDebug.js";
|
|
35
|
-
import { normalizePlanReviewMarkdown } from "../core/planStorage.js";
|
|
35
|
+
import { normalizePlanReviewMarkdown } from "../core/workspace/planStorage.js";
|
|
36
36
|
|
|
37
37
|
export type TurnOpacity = "active" | "recent" | "dim";
|
|
38
38
|
|
|
@@ -69,14 +69,14 @@ function UserInputCard({
|
|
|
69
69
|
dim: boolean;
|
|
70
70
|
}) {
|
|
71
71
|
const theme = useTheme();
|
|
72
|
-
const borderColor = theme.
|
|
72
|
+
const borderColor = theme.border;
|
|
73
73
|
const contentWidth = Math.max(1, cols - 7);
|
|
74
74
|
const lines = wrapPlainText(sanitizeTerminalOutput(prompt), contentWidth);
|
|
75
75
|
|
|
76
76
|
return (
|
|
77
77
|
<DashCard cols={cols} title="PROMPT" borderColor={borderColor}>
|
|
78
78
|
{lines.map((line, i) => (
|
|
79
|
-
<Text key={i} color={dim ? theme.
|
|
79
|
+
<Text key={i} color={dim ? theme.textDim : theme.text}>
|
|
80
80
|
{i === 0 ? "❯ " : " "}{line}
|
|
81
81
|
</Text>
|
|
82
82
|
))}
|
|
@@ -122,38 +122,38 @@ function ImpactSummary({
|
|
|
122
122
|
|
|
123
123
|
const opColor = (op: string) => {
|
|
124
124
|
switch (op) {
|
|
125
|
-
case "created": return theme.
|
|
126
|
-
case "deleted": return theme.
|
|
127
|
-
default: return theme.
|
|
125
|
+
case "created": return theme.success;
|
|
126
|
+
case "deleted": return theme.error;
|
|
127
|
+
default: return theme.info;
|
|
128
128
|
}
|
|
129
129
|
};
|
|
130
130
|
|
|
131
131
|
return (
|
|
132
132
|
<Box flexDirection="column" width="100%" paddingX={1} marginTop={0}>
|
|
133
133
|
{hasDeletes && (
|
|
134
|
-
<Text color={theme.
|
|
134
|
+
<Text color={theme.warning}>{"⚠ Destructive changes detected:"}</Text>
|
|
135
135
|
)}
|
|
136
136
|
{hasFiles && (
|
|
137
137
|
<>
|
|
138
|
-
<Text color={theme.
|
|
138
|
+
<Text color={theme.textDim}>{" Changes:"}</Text>
|
|
139
139
|
{recentFiles.map((file: RunFileActivity, i: number) => {
|
|
140
140
|
const diffInfo = file.addedLines != null || file.removedLines != null
|
|
141
141
|
? ` (+${file.addedLines ?? 0} -${file.removedLines ?? 0})`
|
|
142
142
|
: "";
|
|
143
143
|
return (
|
|
144
144
|
<Text key={i}>
|
|
145
|
-
<Text color={theme.
|
|
145
|
+
<Text color={theme.textDim}>{" "}</Text>
|
|
146
146
|
<Text color={opColor(file.operation)}>{opLabel(file.operation)}</Text>
|
|
147
|
-
<Text color={theme.
|
|
148
|
-
<Text color={theme.
|
|
147
|
+
<Text color={theme.text}>{" "}{file.path}</Text>
|
|
148
|
+
<Text color={theme.textDim}>{diffInfo}</Text>
|
|
149
149
|
</Text>
|
|
150
150
|
);
|
|
151
151
|
})}
|
|
152
152
|
</>
|
|
153
153
|
)}
|
|
154
|
-
<Text color={theme.
|
|
154
|
+
<Text color={theme.textDim}>
|
|
155
155
|
{" "}
|
|
156
|
-
<Text color={theme.
|
|
156
|
+
<Text color={theme.success}>{"✔ "}</Text>
|
|
157
157
|
{run.touchedFileCount > 0 && `${run.touchedFileCount} file${run.touchedFileCount === 1 ? "" : "s"}`}
|
|
158
158
|
{hasTools && `${hasFiles ? " • " : ""}${run.toolActivities.length} action${run.toolActivities.length === 1 ? "" : "s"}`}
|
|
159
159
|
{run.durationMs != null && ` • ${formatDuration(run.durationMs)}`}
|
|
@@ -172,11 +172,11 @@ function FileScanCard({ run, cols }: { run: RunEvent; cols: number }) {
|
|
|
172
172
|
return (
|
|
173
173
|
<DashCard cols={cols} title="Scanning workspace ..." rightBadge={badge}>
|
|
174
174
|
{hiddenCount > 0 && (
|
|
175
|
-
<Text color={theme.
|
|
175
|
+
<Text color={theme.textDim}>{`... ${hiddenCount} more`}</Text>
|
|
176
176
|
)}
|
|
177
177
|
{visible.map((file, i) => (
|
|
178
|
-
<Text key={i} color={theme.
|
|
179
|
-
{"● "}<Text color={theme.
|
|
178
|
+
<Text key={i} color={theme.success}>
|
|
179
|
+
{"● "}<Text color={theme.text}>{file.path}</Text>
|
|
180
180
|
</Text>
|
|
181
181
|
))}
|
|
182
182
|
</DashCard>
|
|
@@ -307,9 +307,9 @@ function PlanPanel({
|
|
|
307
307
|
cols={cols}
|
|
308
308
|
title="Plan"
|
|
309
309
|
rightBadge={approved ? "approved" : undefined}
|
|
310
|
-
borderColor={theme.
|
|
311
|
-
titleColor={theme.
|
|
312
|
-
badgeColor={theme.
|
|
310
|
+
borderColor={theme.accent}
|
|
311
|
+
titleColor={theme.text}
|
|
312
|
+
badgeColor={theme.success}
|
|
313
313
|
>
|
|
314
314
|
<MemoizedRenderMessage segments={formatted} width={contentWidth} brightHeadings />
|
|
315
315
|
</DashCard>
|
|
@@ -337,12 +337,12 @@ function ActionEventCard({
|
|
|
337
337
|
const actionLabel = getFriendlyActionLabel(actionNormalized);
|
|
338
338
|
|
|
339
339
|
const statusIcon = tool.status === "failed" ? "✕" : tool.status === "completed" ? "✔" : "▸";
|
|
340
|
-
const statusColor = tool.status === "failed" ? theme.
|
|
341
|
-
const borderColor = dim ? theme.
|
|
340
|
+
const statusColor = tool.status === "failed" ? theme.error : tool.status === "completed" ? theme.success : theme.info;
|
|
341
|
+
const borderColor = dim ? theme.border : tool.status === "running" ? theme.borderFocused : theme.border;
|
|
342
342
|
const detailText = isLiveCursorTarget && tool.status === "running"
|
|
343
343
|
? "▌"
|
|
344
344
|
: tool.summary?.trim() ? tool.summary : " ";
|
|
345
|
-
const detailColor = isLiveCursorTarget && tool.status === "running" ? theme.
|
|
345
|
+
const detailColor = isLiveCursorTarget && tool.status === "running" ? theme.accent : theme.textMuted;
|
|
346
346
|
const duration = tool.completedAt != null
|
|
347
347
|
? formatDuration(tool.completedAt - tool.startedAt)
|
|
348
348
|
: null;
|
|
@@ -356,10 +356,10 @@ function ActionEventCard({
|
|
|
356
356
|
<>
|
|
357
357
|
<Box>
|
|
358
358
|
<Text color={statusColor}>{statusIcon + " "}</Text>
|
|
359
|
-
<Text color={dim ? theme.
|
|
359
|
+
<Text color={dim ? theme.textDim : theme.text}>{actionLabel}</Text>
|
|
360
360
|
</Box>
|
|
361
361
|
{commandLines.map((line, i) => (
|
|
362
|
-
<Text key={i} color={theme.
|
|
362
|
+
<Text key={i} color={theme.textMuted}>{" "}{line || " "}</Text>
|
|
363
363
|
))}
|
|
364
364
|
</>
|
|
365
365
|
) : (
|
|
@@ -367,10 +367,10 @@ function ActionEventCard({
|
|
|
367
367
|
{commandLines.map((line, i) => (
|
|
368
368
|
<Box key={i}>
|
|
369
369
|
<Text color={i === 0 ? statusColor : undefined}>{i === 0 ? statusIcon + " " : " "}</Text>
|
|
370
|
-
<Text color={dim ? theme.
|
|
370
|
+
<Text color={dim ? theme.textDim : theme.text}>{line || " "}</Text>
|
|
371
371
|
</Box>
|
|
372
372
|
))}
|
|
373
|
-
<Text color={theme.
|
|
373
|
+
<Text color={theme.textMuted}>{" "}</Text>
|
|
374
374
|
</>
|
|
375
375
|
)}
|
|
376
376
|
<Text color={detailColor}>{" "}{detailText}</Text>
|
|
@@ -405,14 +405,14 @@ function CodexThinkingBlock({
|
|
|
405
405
|
|
|
406
406
|
return (
|
|
407
407
|
<Box flexDirection="column" width="100%" paddingLeft={transcriptContentIndent} paddingRight={1}>
|
|
408
|
-
<Text color={theme.
|
|
408
|
+
<Text color={theme.textMuted} bold>Codexa</Text>
|
|
409
409
|
{formatProgressBlockBodyLines(block.text, contentWidth)
|
|
410
410
|
.slice(0, verboseMode ? undefined : COMPACT_PROCESSING_BODY_LINE_CAP)
|
|
411
411
|
.map((line, i) => (
|
|
412
|
-
<Text key={i} color={theme.
|
|
412
|
+
<Text key={i} color={theme.textDim}>{line || " "}</Text>
|
|
413
413
|
))}
|
|
414
414
|
{isLiveCursorTarget && block.status === "active" && (
|
|
415
|
-
<Text color={theme.
|
|
415
|
+
<Text color={theme.accent}>▌</Text>
|
|
416
416
|
)}
|
|
417
417
|
</Box>
|
|
418
418
|
);
|
|
@@ -453,11 +453,11 @@ function CodexResponseBlock({
|
|
|
453
453
|
|
|
454
454
|
return (
|
|
455
455
|
<Box flexDirection="column" width="100%" paddingLeft={transcriptContentIndent} paddingRight={1}>
|
|
456
|
-
<Text color={theme.
|
|
456
|
+
<Text color={theme.textMuted} bold>Codexa</Text>
|
|
457
457
|
{run.status === "failed" && !streaming && isLast && (
|
|
458
458
|
<Box flexDirection="column">
|
|
459
459
|
{wrapPlainText(sanitizeTerminalOutput(run.errorMessage ?? run.summary), contentWidth).map((row, i) => (
|
|
460
|
-
<Text key={i} color={theme.
|
|
460
|
+
<Text key={i} color={theme.error}>{i === 0 ? `✕ ${row}` : ` ${row}`}</Text>
|
|
461
461
|
))}
|
|
462
462
|
</Box>
|
|
463
463
|
)}
|
|
@@ -466,7 +466,7 @@ function CodexResponseBlock({
|
|
|
466
466
|
width={contentWidth}
|
|
467
467
|
/>
|
|
468
468
|
{isLiveCursorTarget && segmentStreaming && (
|
|
469
|
-
<Text color={theme.
|
|
469
|
+
<Text color={theme.accent}>▌</Text>
|
|
470
470
|
)}
|
|
471
471
|
</Box>
|
|
472
472
|
);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import { CODEXA_UPDATE_COMMAND, formatVersionLabel } from "../core/version/updateCheck.js";
|
|
4
|
+
import { clampVisualText } from "./layout.js";
|
|
5
|
+
import { useTheme } from "./theme.js";
|
|
6
|
+
|
|
7
|
+
export const UPDATE_CARD_CONTENT_ROWS = 4; // title + available + using + command
|
|
8
|
+
export const UPDATE_CARD_ROWS = UPDATE_CARD_CONTENT_ROWS + 2; // +2 for top/bottom border rows
|
|
9
|
+
|
|
10
|
+
export interface UpdateAvailableCardProps {
|
|
11
|
+
latestVersion: string;
|
|
12
|
+
currentVersion: string;
|
|
13
|
+
/** Total box width including borders. Long lines are truncated to fit. */
|
|
14
|
+
width?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function UpdateAvailableCard({ latestVersion, currentVersion, width }: UpdateAvailableCardProps) {
|
|
18
|
+
const theme = useTheme();
|
|
19
|
+
const command = `Run: ${CODEXA_UPDATE_COMMAND}`;
|
|
20
|
+
// Inner content width = boxWidth - 2 (left/right border cols)
|
|
21
|
+
const innerWidth = width !== undefined ? Math.max(8, width - 2) : undefined;
|
|
22
|
+
|
|
23
|
+
function clamp(text: string): string {
|
|
24
|
+
return innerWidth !== undefined ? clampVisualText(text, innerWidth) : text;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<Box
|
|
29
|
+
borderStyle="round"
|
|
30
|
+
borderColor={theme.accent}
|
|
31
|
+
flexDirection="column"
|
|
32
|
+
width={width}
|
|
33
|
+
flexShrink={0}
|
|
34
|
+
>
|
|
35
|
+
<Text color={theme.text} bold>{clamp("Update available")}</Text>
|
|
36
|
+
<Text color={theme.textMuted}>{clamp(`Codexa ${formatVersionLabel(latestVersion)}`)}</Text>
|
|
37
|
+
<Text color={theme.textMuted}>{clamp(`Using ${formatVersionLabel(currentVersion)}`)}</Text>
|
|
38
|
+
<Text color={theme.textDim}>{clamp(command)}</Text>
|
|
39
|
+
</Box>
|
|
40
|
+
);
|
|
41
|
+
}
|