ubume 0.1.0

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.
Files changed (182) hide show
  1. package/README.md +88 -0
  2. package/bin/codexa-local-harness-bridge.js +3 -0
  3. package/bin/codexa.js +28 -0
  4. package/bin/ubume-local-harness-bridge.js +325 -0
  5. package/bin/ubume.js +398 -0
  6. package/package.json +66 -0
  7. package/src/app.tsx +5759 -0
  8. package/src/commands/handler.ts +889 -0
  9. package/src/config/appVersion.ts +69 -0
  10. package/src/config/buildInfo.ts +3 -0
  11. package/src/config/launchArgs.ts +196 -0
  12. package/src/config/layeredConfig.ts +853 -0
  13. package/src/config/legacyEnv.ts +16 -0
  14. package/src/config/persistence.ts +377 -0
  15. package/src/config/runtimeConfig.ts +558 -0
  16. package/src/config/settings.ts +405 -0
  17. package/src/config/toml-serialize.ts +109 -0
  18. package/src/config/trustStore.ts +84 -0
  19. package/src/config/updateCheckCache.ts +85 -0
  20. package/src/core/README.md +55 -0
  21. package/src/core/agent/loop.ts +464 -0
  22. package/src/core/agent/protocol.ts +345 -0
  23. package/src/core/agent/tools.ts +423 -0
  24. package/src/core/auth/codexAuth.ts +359 -0
  25. package/src/core/codex/codexExecArgs.ts +166 -0
  26. package/src/core/codex/codexLaunch.ts +163 -0
  27. package/src/core/codex/codexPrompt.ts +429 -0
  28. package/src/core/debug/inputDebug.ts +51 -0
  29. package/src/core/debug/localStreamDebug.ts +50 -0
  30. package/src/core/debug/modelStateDebug.ts +35 -0
  31. package/src/core/executables/antigravityExecutable.ts +48 -0
  32. package/src/core/executables/claudeExecutable.ts +63 -0
  33. package/src/core/executables/codexExecutable.ts +160 -0
  34. package/src/core/executables/executableResolver.ts +164 -0
  35. package/src/core/executables/geminiExecutable.ts +78 -0
  36. package/src/core/models/codexCapabilities.ts +97 -0
  37. package/src/core/models/codexModelCapabilities.ts +624 -0
  38. package/src/core/models/codexModelsCacheSeed.ts +153 -0
  39. package/src/core/models/modelSpecs.ts +303 -0
  40. package/src/core/models/providerModelCache.ts +94 -0
  41. package/src/core/perf/profiler.ts +125 -0
  42. package/src/core/perf/renderDebug.ts +398 -0
  43. package/src/core/process/CommandRunner.ts +280 -0
  44. package/src/core/process/processValidation.ts +111 -0
  45. package/src/core/providerLauncher/launcher.ts +220 -0
  46. package/src/core/providerLauncher/registry.ts +354 -0
  47. package/src/core/providerLauncher/types.ts +95 -0
  48. package/src/core/providerLauncher/workspaceConfig.ts +487 -0
  49. package/src/core/providerRuntime/anthropic.ts +580 -0
  50. package/src/core/providerRuntime/antigravity.ts +500 -0
  51. package/src/core/providerRuntime/capabilityProfile.ts +383 -0
  52. package/src/core/providerRuntime/claudeCodeDiscovery.ts +724 -0
  53. package/src/core/providerRuntime/claudeCodeDiscoveryDebug.ts +55 -0
  54. package/src/core/providerRuntime/codexaCupy.ts +97 -0
  55. package/src/core/providerRuntime/codexaNative.ts +425 -0
  56. package/src/core/providerRuntime/contextMetadata.ts +397 -0
  57. package/src/core/providerRuntime/gemini.ts +789 -0
  58. package/src/core/providerRuntime/lmstudio.ts +118 -0
  59. package/src/core/providerRuntime/local.ts +770 -0
  60. package/src/core/providerRuntime/localHarness/runtime.ts +1090 -0
  61. package/src/core/providerRuntime/localOutputBudget.ts +17 -0
  62. package/src/core/providerRuntime/mistralVibe.ts +667 -0
  63. package/src/core/providerRuntime/models.ts +175 -0
  64. package/src/core/providerRuntime/reasoning.ts +20 -0
  65. package/src/core/providerRuntime/registry.ts +286 -0
  66. package/src/core/providerRuntime/types.ts +142 -0
  67. package/src/core/providerRuntime/unsloth.ts +216 -0
  68. package/src/core/providers/codexJsonStream.ts +305 -0
  69. package/src/core/providers/codexSubprocess.ts +378 -0
  70. package/src/core/providers/codexTranscript.ts +695 -0
  71. package/src/core/providers/openaiNative.ts +13 -0
  72. package/src/core/providers/registry.ts +21 -0
  73. package/src/core/providers/types.ts +91 -0
  74. package/src/core/shared/attachments.ts +101 -0
  75. package/src/core/shared/cleanupFastFail.ts +67 -0
  76. package/src/core/shared/clipboard.ts +24 -0
  77. package/src/core/shared/clipboardImage.ts +111 -0
  78. package/src/core/shared/githubDiagnostics.ts +222 -0
  79. package/src/core/shared/hollowResponseFormat.ts +39 -0
  80. package/src/core/terminal/clearFrameBoundary.ts +852 -0
  81. package/src/core/terminal/frameLock.ts +110 -0
  82. package/src/core/terminal/inkRenderReset.ts +123 -0
  83. package/src/core/terminal/startupClear.ts +20 -0
  84. package/src/core/terminal/terminalCapabilities.ts +100 -0
  85. package/src/core/terminal/terminalControl.ts +169 -0
  86. package/src/core/terminal/terminalSanitize.ts +147 -0
  87. package/src/core/terminal/terminalTitle.ts +400 -0
  88. package/src/core/version/channel.ts +27 -0
  89. package/src/core/version/packageManager.ts +119 -0
  90. package/src/core/version/updateCheck.ts +203 -0
  91. package/src/core/workspace/appData.ts +107 -0
  92. package/src/core/workspace/conversationStore.ts +335 -0
  93. package/src/core/workspace/launchContext.ts +259 -0
  94. package/src/core/workspace/planStorage.ts +135 -0
  95. package/src/core/workspace/projectInstructions.ts +54 -0
  96. package/src/core/workspace/scratchDir.ts +64 -0
  97. package/src/core/workspace/workspaceActivity.ts +384 -0
  98. package/src/core/workspace/workspaceGuard.ts +377 -0
  99. package/src/core/workspace/workspaceRoot.ts +47 -0
  100. package/src/exec.ts +73 -0
  101. package/src/headless/execArgs.ts +296 -0
  102. package/src/headless/execRunner.ts +304 -0
  103. package/src/index.tsx +270 -0
  104. package/src/legacyEnvBootstrap.ts +5 -0
  105. package/src/session/appSession.ts +771 -0
  106. package/src/session/chatLifecycle.ts +994 -0
  107. package/src/session/conversation.ts +107 -0
  108. package/src/session/liveRenderScheduler.ts +214 -0
  109. package/src/session/persistedResponse.ts +93 -0
  110. package/src/session/planFlow.ts +159 -0
  111. package/src/session/planTranscript.ts +19 -0
  112. package/src/session/promptRunSchedule.ts +26 -0
  113. package/src/session/types.ts +234 -0
  114. package/src/test/runtimeTestUtils.ts +14 -0
  115. package/src/types/react-dom.d.ts +3 -0
  116. package/src/ui/chrome/ActivityBars.tsx +68 -0
  117. package/src/ui/chrome/ActivityIndicator.tsx +58 -0
  118. package/src/ui/chrome/AnimatedStatusText.tsx +69 -0
  119. package/src/ui/chrome/AppShell.tsx +474 -0
  120. package/src/ui/chrome/BottomComposer.tsx +1135 -0
  121. package/src/ui/chrome/DashCard.tsx +82 -0
  122. package/src/ui/chrome/RunFooter.tsx +65 -0
  123. package/src/ui/chrome/RuntimeStatusBar.tsx +108 -0
  124. package/src/ui/chrome/Spinner.tsx +25 -0
  125. package/src/ui/chrome/TopHeader.tsx +439 -0
  126. package/src/ui/chrome/UpdateAvailableCard.tsx +42 -0
  127. package/src/ui/chrome/busyStatusAnimation.ts +11 -0
  128. package/src/ui/input/commandNormalize.ts +66 -0
  129. package/src/ui/input/focus.ts +73 -0
  130. package/src/ui/input/imageAttachments.ts +18 -0
  131. package/src/ui/input/inputBuffer.ts +203 -0
  132. package/src/ui/input/pastedContent.ts +75 -0
  133. package/src/ui/input/rawArrowKeys.ts +28 -0
  134. package/src/ui/input/slashCommands.ts +43 -0
  135. package/src/ui/input/useStdinRawModeLease.ts +23 -0
  136. package/src/ui/layout.ts +560 -0
  137. package/src/ui/panels/AttachmentImportPanel.tsx +131 -0
  138. package/src/ui/panels/AuthPanel.tsx +149 -0
  139. package/src/ui/panels/BackendPicker.tsx +28 -0
  140. package/src/ui/panels/ModePicker.tsx +31 -0
  141. package/src/ui/panels/ModelPicker.tsx +31 -0
  142. package/src/ui/panels/ModelPickerScreen.tsx +761 -0
  143. package/src/ui/panels/ModelReasoningPicker.tsx +458 -0
  144. package/src/ui/panels/Panel.tsx +51 -0
  145. package/src/ui/panels/PermissionsPanel.tsx +78 -0
  146. package/src/ui/panels/PlanActionPicker.tsx +187 -0
  147. package/src/ui/panels/ProviderPicker.tsx +753 -0
  148. package/src/ui/panels/ProviderSetupPrompt.tsx +52 -0
  149. package/src/ui/panels/ReasoningPicker.tsx +46 -0
  150. package/src/ui/panels/ResumePicker.tsx +90 -0
  151. package/src/ui/panels/SelectionPanel.tsx +138 -0
  152. package/src/ui/panels/SettingsPanel.tsx +156 -0
  153. package/src/ui/panels/TextEntryPanel.tsx +139 -0
  154. package/src/ui/panels/ThemePicker.tsx +32 -0
  155. package/src/ui/panels/ToolApprovalPanel.tsx +46 -0
  156. package/src/ui/panels/UpdatePromptPanel.tsx +236 -0
  157. package/src/ui/panels/responsivePickerViewport.ts +64 -0
  158. package/src/ui/render/Markdown.tsx +331 -0
  159. package/src/ui/render/diffRenderer.ts +116 -0
  160. package/src/ui/render/logoVariants.ts +113 -0
  161. package/src/ui/render/modeDisplay.ts +52 -0
  162. package/src/ui/render/outputPipeline.ts +64 -0
  163. package/src/ui/render/runtimeDisplay.ts +128 -0
  164. package/src/ui/render/terminalAnswerFormat.ts +128 -0
  165. package/src/ui/render/textLayout.ts +392 -0
  166. package/src/ui/theme.tsx +274 -0
  167. package/src/ui/themeFlow.ts +41 -0
  168. package/src/ui/timeline/ActionRequiredBlock.tsx +38 -0
  169. package/src/ui/timeline/AgentBlock.tsx +130 -0
  170. package/src/ui/timeline/StaticIntroItem.tsx +54 -0
  171. package/src/ui/timeline/ThinkingBlock.tsx +100 -0
  172. package/src/ui/timeline/Timeline.tsx +1410 -0
  173. package/src/ui/timeline/TranscriptShell.tsx +302 -0
  174. package/src/ui/timeline/TurnGroup.tsx +673 -0
  175. package/src/ui/timeline/layoutListWindow.ts +145 -0
  176. package/src/ui/timeline/liveViewportWindow.ts +68 -0
  177. package/src/ui/timeline/progressEntries.ts +156 -0
  178. package/src/ui/timeline/runActivityView.ts +37 -0
  179. package/src/ui/timeline/staticTranscriptCache.ts +174 -0
  180. package/src/ui/timeline/streamCoalesce.ts +53 -0
  181. package/src/ui/timeline/timelineMeasure.ts +3273 -0
  182. package/src/ui/useThrottledValue.ts +31 -0
@@ -0,0 +1,38 @@
1
+ import React from "react";
2
+ import { Box, Text } from "ink";
3
+ import { getUsableShellWidth } from "../layout.js";
4
+ import { wrapPlainText } from "../render/textLayout.js";
5
+ import { useTheme } from "../theme.js";
6
+
7
+ interface ActionRequiredBlockProps {
8
+ cols: number;
9
+ turnIndex: number;
10
+ question: string;
11
+ }
12
+
13
+ export function ActionRequiredBlock({ cols, turnIndex, question }: ActionRequiredBlockProps) {
14
+ const theme = useTheme();
15
+ const contentWidth = Math.max(1, getUsableShellWidth(cols, 6));
16
+
17
+ const wrappedContent = question
18
+ .split("\n")
19
+ .flatMap((line) => {
20
+ const rows = wrapPlainText(line, contentWidth);
21
+ return rows.length > 0 ? rows : [""];
22
+ });
23
+
24
+ return (
25
+ <Box borderStyle="single" borderColor={theme.borderFocused} flexDirection="column" marginBottom={1} width="100%">
26
+ <Box width="100%" justifyContent="space-between" overflow="hidden" paddingX={1}>
27
+ <Text color={theme.text} bold>{`[${turnIndex}] ACTION REQUIRED`}</Text>
28
+ <Text color={theme.text} bold>{"⚡"}</Text>
29
+ </Box>
30
+ <Box flexDirection="column" paddingX={2} marginTop={1} marginBottom={1} width="100%">
31
+ <Text color={theme.text} bold>{"Verification Question"}</Text>
32
+ {wrappedContent.map((row, index) => (
33
+ <Text key={`content-${index}`} color={theme.text}>{row || " "}</Text>
34
+ ))}
35
+ </Box>
36
+ </Box>
37
+ );
38
+ }
@@ -0,0 +1,130 @@
1
+ import React, { memo, useDeferredValue, useMemo } from "react";
2
+ import { Box, Text } from "ink";
3
+ import type { AssistantEvent, RunEvent } from "../../session/types.js";
4
+ import { getAssistantContent } from "../../session/types.js";
5
+ import { MemoizedRenderMessage } from "../render/Markdown.js";
6
+ import { getUsableShellWidth } from "../layout.js";
7
+ import { useTheme } from "../theme.js";
8
+ import { wrapPlainText } from "../render/textLayout.js";
9
+ import { RUN_OUTPUT_TRUNCATION_NOTICE } from "../../session/chatLifecycle.js";
10
+ import { sanitizeTerminalOutput } from "../../core/terminal/terminalSanitize.js";
11
+ import {
12
+ sanitizeOutput,
13
+ sanitizeStreamChunk,
14
+ normalizeOutput,
15
+ classifyOutput,
16
+ formatForBox,
17
+ } from "../render/outputPipeline.js";
18
+ import { DashCard } from "../chrome/DashCard.js";
19
+
20
+
21
+ function formatDuration(ms: number): string {
22
+ if (ms < 1000) return `${ms}ms`;
23
+ return `${(ms / 1000).toFixed(1)}s`;
24
+ }
25
+
26
+ interface AgentBlockProps {
27
+ cols: number;
28
+ assistant: AssistantEvent | null;
29
+ run: RunEvent | null;
30
+ streaming: boolean;
31
+ turnIndex: number;
32
+ dim?: boolean;
33
+ runPhase?: "streaming" | "final";
34
+ streamingPreviewRows?: number;
35
+ streamingMode?: "assistant-first";
36
+ }
37
+
38
+ const MemoizedMessageBody = memo(function MessageBody({
39
+ segments,
40
+ width,
41
+ }: {
42
+ segments: ReturnType<typeof formatForBox>;
43
+ width: number;
44
+ }) {
45
+ return <MemoizedRenderMessage segments={segments} width={width} />;
46
+ }, (prev, next) => prev.segments === next.segments && prev.width === next.width);
47
+
48
+ const StreamingCursor = memo(function StreamingCursor() {
49
+ const theme = useTheme();
50
+ return (
51
+ <Box width="100%" paddingLeft={2}>
52
+ <Text color={theme.accent}>{"▌"}</Text>
53
+ </Box>
54
+ );
55
+ });
56
+
57
+ export function AgentBlock({
58
+ cols,
59
+ assistant,
60
+ run,
61
+ streaming,
62
+ dim = false,
63
+ runPhase = streaming ? "streaming" : "final",
64
+ }: AgentBlockProps) {
65
+ const theme = useTheme();
66
+ const content = getAssistantContent(assistant);
67
+ const deferredContent = useDeferredValue(content);
68
+ // During streaming, use content directly for immediate rendering.
69
+ // When not streaming, defer large final content to avoid blocking input.
70
+ const renderContent = streaming ? content : deferredContent;
71
+ const contentWidth = Math.max(1, getUsableShellWidth(cols, 4));
72
+
73
+ const pipelineState = useMemo(() => {
74
+ const sanitized = streaming ? sanitizeStreamChunk(renderContent) : sanitizeOutput(renderContent);
75
+ const normalized = normalizeOutput(sanitized);
76
+ const classified = classifyOutput(normalized);
77
+ const formatted = formatForBox(classified, contentWidth);
78
+ return { length: normalized.length, formatted };
79
+ }, [contentWidth, renderContent, streaming]);
80
+
81
+ const failureMessage = run?.status === "failed"
82
+ ? sanitizeTerminalOutput(run.errorMessage ?? run.summary)
83
+ : null;
84
+ const cancelMessage = run?.status === "canceled" ? sanitizeTerminalOutput(run.summary) : null;
85
+
86
+ const runStatus = runPhase === "streaming"
87
+ ? "streaming"
88
+ : run?.status === "completed"
89
+ ? "complete"
90
+ : run?.status ?? "running";
91
+ const rightBadge = run?.durationMs != null && runPhase !== "streaming"
92
+ ? `${runStatus} • ${formatDuration(run.durationMs)}`
93
+ : runStatus;
94
+ const heading = run?.runtime.model ? run.runtime.model.toUpperCase().replace(/-/g, " ") : "Codex";
95
+
96
+ const borderColor = dim ? theme.border : (runPhase === "streaming" ? theme.borderFocused : theme.border);
97
+
98
+ return (
99
+ <DashCard cols={cols} title={heading} rightBadge={rightBadge} borderColor={borderColor}>
100
+ {!streaming && failureMessage && (
101
+ <Box flexDirection="column" width="100%">
102
+ {wrapPlainText(failureMessage, contentWidth).map((row, index) => (
103
+ <Text key={index} color={theme.error}>{index === 0 ? `✕ ${row || " "}` : row || " "}</Text>
104
+ ))}
105
+ </Box>
106
+ )}
107
+
108
+ {pipelineState.length > 0 && (
109
+ <Box flexDirection="column" width="100%">
110
+ <MemoizedMessageBody segments={pipelineState.formatted} width={contentWidth} />
111
+ </Box>
112
+ )}
113
+
114
+ {streaming && <StreamingCursor />}
115
+
116
+ {!streaming && run && run.status !== "running" && (
117
+ <Box flexDirection="column" width="100%">
118
+ {run.status === "canceled" && cancelMessage ? (
119
+ <Text color={theme.warning}>{cancelMessage}</Text>
120
+ ) : run.status === "completed" && pipelineState.length === 0 ? (
121
+ <Text color={theme.textDim}>{"(no output)"}</Text>
122
+ ) : null}
123
+ {run.truncatedOutput && (
124
+ <Text color={theme.textDim}>{RUN_OUTPUT_TRUNCATION_NOTICE}</Text>
125
+ )}
126
+ </Box>
127
+ )}
128
+ </DashCard>
129
+ );
130
+ }
@@ -0,0 +1,54 @@
1
+ import React from "react";
2
+ import { Box } from "ink";
3
+ import type { CodexAuthState } from "../../core/auth/codexAuth.js";
4
+ import { getShellWidth, type Layout, type StartupHeaderMode } from "../layout.js";
5
+ import { buildIntroRenderItem, TimelineRowView } from "./Timeline.js";
6
+ import type { TimelineRow } from "./timelineMeasure.js";
7
+ import { buildTimelineSnapshot } from "./timelineMeasure.js";
8
+
9
+ interface StaticIntroItemProps {
10
+ authState: CodexAuthState;
11
+ workspaceLabel: string;
12
+ layout: Layout;
13
+ startupHeaderMode?: StartupHeaderMode;
14
+ verboseMode: boolean;
15
+ workspaceRoot: string | null;
16
+ }
17
+
18
+ export function buildStaticIntroRows({
19
+ authState,
20
+ workspaceLabel,
21
+ layout,
22
+ startupHeaderMode,
23
+ verboseMode,
24
+ workspaceRoot,
25
+ }: StaticIntroItemProps): TimelineRow[] {
26
+ const introItem = buildIntroRenderItem({ authState, workspaceLabel, layout, startupHeaderMode });
27
+ return buildTimelineSnapshot([introItem], {
28
+ totalWidth: getShellWidth(layout.cols),
29
+ verboseMode,
30
+ debugLabel: "static-intro",
31
+ workspaceRoot,
32
+ }).rows;
33
+ }
34
+
35
+ export function StaticIntroItem(props: StaticIntroItemProps) {
36
+ const rows = React.useMemo(() => buildStaticIntroRows(props), [
37
+ props.authState,
38
+ props.workspaceLabel,
39
+ props.layout.cols,
40
+ props.layout.rows,
41
+ props.layout.mode,
42
+ props.startupHeaderMode,
43
+ props.verboseMode,
44
+ props.workspaceRoot,
45
+ ]);
46
+
47
+ return (
48
+ <Box flexDirection="column">
49
+ {rows.map((row) => (
50
+ <TimelineRowView key={row.key} row={row} />
51
+ ))}
52
+ </Box>
53
+ );
54
+ }
@@ -0,0 +1,100 @@
1
+ import React from "react";
2
+ import { Box, Text } from "ink";
3
+ import type { RunEvent } from "../../session/types.js";
4
+ import { clampVisualText, getUsableShellWidth } from "../layout.js";
5
+ import { useTheme } from "../theme.js";
6
+ import { DashCard } from "../chrome/DashCard.js";
7
+ import {
8
+ formatProgressBlockBodyLines,
9
+ getProgressUpdateCount,
10
+ selectVisibleProgressBlocks,
11
+ type VisibleProgressBlock,
12
+ } from "./progressEntries.js";
13
+
14
+ export const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] as const;
15
+
16
+ const MAX_VISIBLE_PROGRESS_ENTRIES = 3;
17
+
18
+ interface ThinkingBlockProps {
19
+ cols: number;
20
+ run: RunEvent;
21
+ turnIndex: number;
22
+ }
23
+
24
+ function getBlockMarker(isLive: boolean): string {
25
+ return isLive ? "▸" : "•";
26
+ }
27
+
28
+ export function ThinkingBlock({ cols, run }: ThinkingBlockProps) {
29
+ const theme = useTheme();
30
+ const {
31
+ blocks,
32
+ hiddenCount,
33
+ totalCount,
34
+ latestBlock,
35
+ latestActiveBlock,
36
+ } = selectVisibleProgressBlocks(run.progressEntries ?? [], MAX_VISIBLE_PROGRESS_ENTRIES);
37
+ const contentWidth = Math.max(1, getUsableShellWidth(cols, 8));
38
+ const updateCount = totalCount || getProgressUpdateCount(run.progressEntries ?? []);
39
+ const currentBlock = latestActiveBlock ?? latestBlock;
40
+ const currentText = currentBlock
41
+ ? clampVisualText(currentBlock.headline.replace(/^Current:\s*/i, ""), Math.max(1, contentWidth - 9))
42
+ : null;
43
+ const rightBadge = run.status === "running"
44
+ ? "active"
45
+ : `${updateCount} update${updateCount === 1 ? "" : "s"}`;
46
+
47
+ if (blocks.length === 0 && run.status === "running") {
48
+ return null;
49
+ }
50
+
51
+ return (
52
+ <DashCard
53
+ cols={cols}
54
+ title="Processing"
55
+ rightBadge={rightBadge}
56
+ borderColor={run.status === "running" ? theme.borderFocused : theme.border}
57
+ >
58
+ {blocks.length === 0 ? (
59
+ <Text color={theme.textDim}>No processing updates</Text>
60
+ ) : (
61
+ <Box flexDirection="column" width="100%">
62
+ {currentText && run.status === "running" && (
63
+ <Box width="100%">
64
+ <Text color={theme.info} bold>Current: </Text>
65
+ <Text color={theme.text}>{currentText}</Text>
66
+ </Box>
67
+ )}
68
+ {hiddenCount > 0 && (
69
+ <Box marginTop={currentText && run.status === "running" ? 1 : 0}>
70
+ <Text color={theme.textDim}>{`... ${hiddenCount} earlier update${hiddenCount === 1 ? "" : "s"}`}</Text>
71
+ </Box>
72
+ )}
73
+ {blocks.map((block, blockIndex) => {
74
+ const isLive = run.status === "running" && block.isActive;
75
+ return (
76
+ <Box
77
+ key={block.key}
78
+ flexDirection="column"
79
+ width="100%"
80
+ marginTop={blockIndex === 0 && hiddenCount === 0 && !(currentText && run.status === "running") ? 0 : 1}
81
+ >
82
+ <Text color={isLive ? theme.accent : theme.info} bold={isLive}>
83
+ {`${getBlockMarker(isLive)} ${isLive ? "Live" : block.label}`}
84
+ </Text>
85
+ {formatProgressBlockBodyLines(block.text, contentWidth).map((line, lineIndex) => (
86
+ <Text key={`${block.key}-${lineIndex}`} color={theme.textMuted}>
87
+ {line ? `${isLive ? " | " : " "}${line}` : " "}
88
+ </Text>
89
+ ))}
90
+ {isLive && (
91
+ <Text color={theme.accent}> | ▌</Text>
92
+ )}
93
+ </Box>
94
+ );
95
+ })}
96
+ </Box>
97
+ )}
98
+ </DashCard>
99
+ );
100
+ }