@golba98/codexa 1.0.1
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 +320 -0
- package/bin/codexa.js +445 -0
- package/package.json +45 -0
- package/scripts/audit-codexa-capabilities.mjs +466 -0
- package/scripts/smoke-terminal-bench.mjs +35 -0
- package/src/app.tsx +4561 -0
- package/src/appRenderStability.test.ts +131 -0
- package/src/commands/handler.test.ts +643 -0
- package/src/commands/handler.ts +875 -0
- package/src/config/launchArgs.test.ts +158 -0
- package/src/config/launchArgs.ts +186 -0
- package/src/config/layeredConfig.test.ts +143 -0
- package/src/config/layeredConfig.ts +836 -0
- package/src/config/persistence.test.ts +110 -0
- package/src/config/persistence.ts +311 -0
- package/src/config/runtimeConfig.test.ts +218 -0
- package/src/config/runtimeConfig.ts +554 -0
- package/src/config/settings.test.ts +155 -0
- package/src/config/settings.ts +401 -0
- package/src/config/toml-serialize.ts +98 -0
- package/src/config/trustStore.test.ts +29 -0
- package/src/config/trustStore.ts +68 -0
- package/src/core/attachments.test.ts +155 -0
- package/src/core/attachments.ts +71 -0
- package/src/core/auth/codexAuth.test.ts +68 -0
- package/src/core/auth/codexAuth.ts +359 -0
- package/src/core/cleanupFastFail.test.ts +76 -0
- package/src/core/cleanupFastFail.ts +67 -0
- package/src/core/clipboard.ts +24 -0
- package/src/core/codex.ts +124 -0
- package/src/core/codexExecArgs.test.ts +195 -0
- package/src/core/codexExecArgs.ts +152 -0
- package/src/core/codexLaunch.test.ts +205 -0
- package/src/core/codexLaunch.ts +162 -0
- package/src/core/codexPrompt.test.ts +252 -0
- package/src/core/codexPrompt.ts +428 -0
- package/src/core/executables/claudeExecutable.ts +63 -0
- package/src/core/executables/codexExecutable.test.ts +212 -0
- package/src/core/executables/codexExecutable.ts +159 -0
- package/src/core/executables/executableResolver.test.ts +129 -0
- package/src/core/executables/executableResolver.ts +138 -0
- package/src/core/executables/geminiExecutable.test.ts +116 -0
- package/src/core/executables/geminiExecutable.ts +78 -0
- package/src/core/executables/pathSanityScan.test.ts +47 -0
- package/src/core/githubDiagnostics.test.ts +92 -0
- package/src/core/githubDiagnostics.ts +222 -0
- package/src/core/hollowResponseFormat.test.ts +58 -0
- package/src/core/hollowResponseFormat.ts +39 -0
- package/src/core/inputDebug.ts +51 -0
- package/src/core/launchContext.test.ts +157 -0
- package/src/core/launchContext.ts +266 -0
- package/src/core/models/codexCapabilities.test.ts +45 -0
- package/src/core/models/codexCapabilities.ts +95 -0
- package/src/core/models/codexModelCapabilities.test.ts +246 -0
- package/src/core/models/codexModelCapabilities.ts +571 -0
- package/src/core/models/modelSpecs.test.ts +283 -0
- package/src/core/models/modelSpecs.ts +300 -0
- package/src/core/perf/profiler.ts +125 -0
- package/src/core/perf/renderDebug.test.ts +230 -0
- package/src/core/perf/renderDebug.ts +373 -0
- package/src/core/planStorage.test.ts +143 -0
- package/src/core/planStorage.ts +141 -0
- package/src/core/process/CommandRunner.test.ts +105 -0
- package/src/core/process/CommandRunner.ts +269 -0
- package/src/core/process/processValidation.ts +101 -0
- package/src/core/projectInstructions.test.ts +50 -0
- package/src/core/projectInstructions.ts +54 -0
- package/src/core/providerLauncher/launcher.test.ts +238 -0
- package/src/core/providerLauncher/launcher.ts +203 -0
- package/src/core/providerLauncher/registry.test.ts +324 -0
- package/src/core/providerLauncher/registry.ts +253 -0
- package/src/core/providerLauncher/types.ts +84 -0
- package/src/core/providerLauncher/workspaceConfig.test.ts +638 -0
- package/src/core/providerLauncher/workspaceConfig.ts +407 -0
- package/src/core/providerRuntime/anthropic.test.ts +1120 -0
- package/src/core/providerRuntime/anthropic.ts +576 -0
- package/src/core/providerRuntime/capabilityProfile.test.ts +311 -0
- package/src/core/providerRuntime/capabilityProfile.ts +288 -0
- package/src/core/providerRuntime/claudeCodeDiscovery.ts +446 -0
- package/src/core/providerRuntime/contextMetadata.test.ts +468 -0
- package/src/core/providerRuntime/contextMetadata.ts +409 -0
- package/src/core/providerRuntime/gemini.test.ts +437 -0
- package/src/core/providerRuntime/gemini.ts +784 -0
- package/src/core/providerRuntime/lmstudio.test.ts +168 -0
- package/src/core/providerRuntime/lmstudio.ts +118 -0
- package/src/core/providerRuntime/local.test.ts +787 -0
- package/src/core/providerRuntime/local.ts +754 -0
- package/src/core/providerRuntime/models.ts +150 -0
- package/src/core/providerRuntime/reasoning.ts +17 -0
- package/src/core/providerRuntime/registry.test.ts +233 -0
- package/src/core/providerRuntime/registry.ts +203 -0
- package/src/core/providerRuntime/types.ts +103 -0
- package/src/core/providers/codexJsonStream.test.ts +148 -0
- package/src/core/providers/codexJsonStream.ts +305 -0
- package/src/core/providers/codexSubprocess.test.ts +68 -0
- package/src/core/providers/codexSubprocess.ts +372 -0
- package/src/core/providers/codexTranscript.test.ts +284 -0
- package/src/core/providers/codexTranscript.ts +695 -0
- package/src/core/providers/openaiNative.ts +13 -0
- package/src/core/providers/registry.ts +21 -0
- package/src/core/providers/types.ts +59 -0
- package/src/core/terminal/terminalCapabilities.test.ts +93 -0
- package/src/core/terminal/terminalCapabilities.ts +100 -0
- package/src/core/terminal/terminalControl.test.ts +75 -0
- package/src/core/terminal/terminalControl.ts +147 -0
- package/src/core/terminal/terminalSanitize.test.ts +22 -0
- package/src/core/terminal/terminalSanitize.ts +147 -0
- package/src/core/terminal/terminalSelection.test.ts +42 -0
- package/src/core/terminal/terminalSelection.ts +66 -0
- package/src/core/terminal/terminalTitle.test.ts +328 -0
- package/src/core/terminal/terminalTitle.ts +483 -0
- package/src/core/workspaceActivity.test.ts +163 -0
- package/src/core/workspaceActivity.ts +380 -0
- package/src/core/workspaceGuard.test.ts +151 -0
- package/src/core/workspaceGuard.ts +288 -0
- package/src/core/workspaceRoot.test.ts +23 -0
- package/src/core/workspaceRoot.ts +47 -0
- package/src/exec.test.ts +13 -0
- package/src/exec.ts +72 -0
- package/src/headless/execArgs.test.ts +147 -0
- package/src/headless/execArgs.ts +294 -0
- package/src/headless/execRunner.test.ts +434 -0
- package/src/headless/execRunner.ts +304 -0
- package/src/index.test.tsx +618 -0
- package/src/index.tsx +296 -0
- package/src/session/appSession.test.ts +897 -0
- package/src/session/appSession.ts +761 -0
- package/src/session/chatLifecycle.test.ts +64 -0
- package/src/session/chatLifecycle.ts +951 -0
- package/src/session/liveRenderScheduler.test.ts +201 -0
- package/src/session/liveRenderScheduler.ts +214 -0
- package/src/session/planFlow.test.ts +103 -0
- package/src/session/planFlow.ts +149 -0
- package/src/session/planTranscript.test.ts +65 -0
- package/src/session/planTranscript.ts +15 -0
- package/src/session/promptRunSchedule.test.ts +36 -0
- package/src/session/promptRunSchedule.ts +26 -0
- package/src/session/types.ts +228 -0
- package/src/test/runtimeTestUtils.ts +14 -0
- package/src/types/react-dom.d.ts +3 -0
- package/src/ui/ActionRequiredBlock.tsx +38 -0
- package/src/ui/ActivityBars.tsx +68 -0
- package/src/ui/ActivityIndicator.test.tsx +58 -0
- package/src/ui/ActivityIndicator.tsx +58 -0
- package/src/ui/AgentBlock.test.ts +6 -0
- package/src/ui/AgentBlock.tsx +130 -0
- package/src/ui/AnimatedStatusText.test.ts +16 -0
- package/src/ui/AnimatedStatusText.tsx +69 -0
- package/src/ui/AppShell.test.tsx +1739 -0
- package/src/ui/AppShell.tsx +698 -0
- package/src/ui/AttachmentImportPanel.test.tsx +204 -0
- package/src/ui/AttachmentImportPanel.tsx +98 -0
- package/src/ui/AuthPanel.tsx +113 -0
- package/src/ui/BackendPicker.tsx +28 -0
- package/src/ui/BottomComposer.test.ts +674 -0
- package/src/ui/BottomComposer.tsx +1028 -0
- package/src/ui/CodexLogo.tsx +55 -0
- package/src/ui/DashCard.tsx +82 -0
- package/src/ui/Markdown.test.ts +157 -0
- package/src/ui/Markdown.tsx +310 -0
- package/src/ui/ModePicker.tsx +27 -0
- package/src/ui/ModelPicker.tsx +31 -0
- package/src/ui/ModelPickerProviderScope.test.tsx +411 -0
- package/src/ui/ModelPickerScreen.test.tsx +99 -0
- package/src/ui/ModelPickerScreen.tsx +416 -0
- package/src/ui/ModelPickerState.test.tsx +151 -0
- package/src/ui/ModelReasoningPicker.test.tsx +447 -0
- package/src/ui/ModelReasoningPicker.tsx +458 -0
- package/src/ui/Panel.tsx +51 -0
- package/src/ui/PermissionsPanel.tsx +78 -0
- package/src/ui/PlanActionPicker.tsx +119 -0
- package/src/ui/PlanReviewPanel.test.tsx +267 -0
- package/src/ui/PlanReviewPanel.tsx +212 -0
- package/src/ui/PromptCardBorder.test.tsx +161 -0
- package/src/ui/ProviderPicker.test.tsx +289 -0
- package/src/ui/ProviderPicker.tsx +321 -0
- package/src/ui/ProviderShortcut.test.tsx +143 -0
- package/src/ui/ReasoningPicker.tsx +46 -0
- package/src/ui/RunFooter.tsx +65 -0
- package/src/ui/SelectionPanel.tsx +67 -0
- package/src/ui/SettingsPanel.test.tsx +233 -0
- package/src/ui/SettingsPanel.tsx +156 -0
- package/src/ui/Spinner.tsx +25 -0
- package/src/ui/StaticIntroItem.tsx +54 -0
- package/src/ui/StaticTranscriptItem.tsx +56 -0
- package/src/ui/TextEntryPanel.tsx +139 -0
- package/src/ui/ThemePicker.tsx +31 -0
- package/src/ui/ThinkingBlock.tsx +100 -0
- package/src/ui/Timeline.test.ts +2067 -0
- package/src/ui/Timeline.tsx +1472 -0
- package/src/ui/TimelineNavigation.test.tsx +201 -0
- package/src/ui/TopHeader.test.tsx +239 -0
- package/src/ui/TopHeader.tsx +257 -0
- package/src/ui/TurnGroup.test.tsx +365 -0
- package/src/ui/TurnGroup.tsx +657 -0
- package/src/ui/busyStatusAnimation.test.ts +30 -0
- package/src/ui/busyStatusAnimation.ts +11 -0
- package/src/ui/commandNormalize.test.ts +142 -0
- package/src/ui/commandNormalize.ts +66 -0
- package/src/ui/diffRenderer.test.ts +102 -0
- package/src/ui/diffRenderer.ts +116 -0
- package/src/ui/focus.ts +61 -0
- package/src/ui/focusFlow.test.tsx +1098 -0
- package/src/ui/inputBuffer.test.ts +151 -0
- package/src/ui/inputBuffer.ts +203 -0
- package/src/ui/layout.test.ts +145 -0
- package/src/ui/layout.ts +287 -0
- package/src/ui/modeDisplay.test.ts +42 -0
- package/src/ui/modeDisplay.ts +52 -0
- package/src/ui/outputPipeline.ts +64 -0
- package/src/ui/progressEntries.ts +156 -0
- package/src/ui/runActivityView.test.ts +89 -0
- package/src/ui/runActivityView.ts +37 -0
- package/src/ui/runLifecycleView.test.tsx +237 -0
- package/src/ui/slashCommands.ts +41 -0
- package/src/ui/statusRenderIsolation.test.tsx +654 -0
- package/src/ui/terminalAnswerFormat.test.ts +19 -0
- package/src/ui/terminalAnswerFormat.ts +128 -0
- package/src/ui/textLayout.test.ts +18 -0
- package/src/ui/textLayout.ts +338 -0
- package/src/ui/theme.tsx +395 -0
- package/src/ui/themeFlow.test.ts +53 -0
- package/src/ui/themeFlow.ts +41 -0
- package/src/ui/timelineMeasure.ts +3088 -0
- package/src/ui/timelineMeasureCache.test.ts +986 -0
- package/src/ui/useThrottledValue.ts +31 -0
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { PassThrough } from "node:stream";
|
|
5
|
+
import { render } from "ink";
|
|
6
|
+
import type { AssistantEvent, RunEvent, UIState, UserPromptEvent } from "../session/types.js";
|
|
7
|
+
import { TEST_RUNTIME } from "../test/runtimeTestUtils.js";
|
|
8
|
+
import { ThemeProvider } from "./theme.js";
|
|
9
|
+
import { TurnGroup, resolveTurnRunPhase } from "./TurnGroup.js";
|
|
10
|
+
|
|
11
|
+
class TestInput extends PassThrough {
|
|
12
|
+
readonly isTTY = true;
|
|
13
|
+
|
|
14
|
+
setRawMode(): this {
|
|
15
|
+
return this;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
override resume(): this {
|
|
19
|
+
return this;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
override pause(): this {
|
|
23
|
+
return this;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
ref(): this {
|
|
27
|
+
return this;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
unref(): this {
|
|
31
|
+
return this;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
class TestOutput extends PassThrough {
|
|
36
|
+
readonly isTTY = true;
|
|
37
|
+
columns = 120;
|
|
38
|
+
rows = 40;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function stripAnsi(value: string): string {
|
|
42
|
+
return value.replace(/\u001B\[[0-?]*[ -/]*[@-~]/g, "");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function sleep(ms = 50): Promise<void> {
|
|
46
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function makeRunningRun(turnId: number): RunEvent {
|
|
50
|
+
return {
|
|
51
|
+
id: 2,
|
|
52
|
+
type: "run",
|
|
53
|
+
createdAt: 2,
|
|
54
|
+
startedAt: 2,
|
|
55
|
+
durationMs: null,
|
|
56
|
+
backendId: "codex-subprocess",
|
|
57
|
+
backendLabel: "Codexa",
|
|
58
|
+
runtime: TEST_RUNTIME,
|
|
59
|
+
prompt: "Do work",
|
|
60
|
+
progressEntries: [],
|
|
61
|
+
status: "running",
|
|
62
|
+
summary: "Running",
|
|
63
|
+
truncatedOutput: false,
|
|
64
|
+
toolActivities: [],
|
|
65
|
+
activity: [],
|
|
66
|
+
touchedFileCount: 0,
|
|
67
|
+
errorMessage: null,
|
|
68
|
+
turnId,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function makeUser(turnId: number): UserPromptEvent {
|
|
73
|
+
return {
|
|
74
|
+
id: 1,
|
|
75
|
+
type: "user",
|
|
76
|
+
createdAt: 1,
|
|
77
|
+
prompt: "Do work",
|
|
78
|
+
turnId,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function makeAssistant(turnId: number, content: string): AssistantEvent {
|
|
83
|
+
return {
|
|
84
|
+
id: 3,
|
|
85
|
+
type: "assistant",
|
|
86
|
+
createdAt: 3,
|
|
87
|
+
content,
|
|
88
|
+
contentChunks: [],
|
|
89
|
+
turnId,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function renderTurnGroup(node: React.ReactElement) {
|
|
94
|
+
const stdin = new TestInput();
|
|
95
|
+
const stdout = new TestOutput();
|
|
96
|
+
let output = "";
|
|
97
|
+
stdout.on("data", (chunk) => {
|
|
98
|
+
output += chunk.toString();
|
|
99
|
+
});
|
|
100
|
+
const instance = render(node, {
|
|
101
|
+
stdin: stdin as unknown as NodeJS.ReadStream,
|
|
102
|
+
stdout: stdout as unknown as NodeJS.WriteStream,
|
|
103
|
+
stderr: stdout as unknown as NodeJS.WriteStream,
|
|
104
|
+
debug: true,
|
|
105
|
+
exitOnCtrlC: false,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
instance,
|
|
110
|
+
readOutput: () => stripAnsi(output),
|
|
111
|
+
resetOutput: () => {
|
|
112
|
+
output = "";
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
test("does not render duplicate transcript working placeholder before streaming appears", async () => {
|
|
118
|
+
const turnId = 10;
|
|
119
|
+
const user = makeUser(turnId);
|
|
120
|
+
const run = makeRunningRun(turnId);
|
|
121
|
+
const assistant = makeAssistant(turnId, "Streaming line");
|
|
122
|
+
|
|
123
|
+
const harness = renderTurnGroup(
|
|
124
|
+
<ThemeProvider theme="purple">
|
|
125
|
+
<TurnGroup
|
|
126
|
+
cols={120}
|
|
127
|
+
turnIndex={1}
|
|
128
|
+
user={user}
|
|
129
|
+
run={run}
|
|
130
|
+
assistant={null}
|
|
131
|
+
opacity="active"
|
|
132
|
+
question={null}
|
|
133
|
+
runPhase="thinking"
|
|
134
|
+
streamPreviewRows={8}
|
|
135
|
+
streamMode="assistant-first"
|
|
136
|
+
/>
|
|
137
|
+
</ThemeProvider>,
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
await sleep();
|
|
141
|
+
let frame = harness.readOutput();
|
|
142
|
+
assert.doesNotMatch(frame, /Codex is working/i);
|
|
143
|
+
|
|
144
|
+
harness.resetOutput();
|
|
145
|
+
harness.instance.rerender(
|
|
146
|
+
<ThemeProvider theme="purple">
|
|
147
|
+
<TurnGroup
|
|
148
|
+
cols={120}
|
|
149
|
+
turnIndex={1}
|
|
150
|
+
user={user}
|
|
151
|
+
run={run}
|
|
152
|
+
assistant={assistant}
|
|
153
|
+
opacity="active"
|
|
154
|
+
question={null}
|
|
155
|
+
runPhase="streaming"
|
|
156
|
+
streamPreviewRows={8}
|
|
157
|
+
streamMode="assistant-first"
|
|
158
|
+
/>
|
|
159
|
+
</ThemeProvider>,
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
await sleep();
|
|
163
|
+
frame = harness.readOutput();
|
|
164
|
+
assert.match(frame, /Codexa/);
|
|
165
|
+
assert.match(frame, /Streaming line/i);
|
|
166
|
+
|
|
167
|
+
harness.instance.unmount();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("renders progressive content updates during streaming", async () => {
|
|
171
|
+
const turnId = 12;
|
|
172
|
+
const user = makeUser(turnId);
|
|
173
|
+
const run = makeRunningRun(turnId);
|
|
174
|
+
|
|
175
|
+
const harness = renderTurnGroup(
|
|
176
|
+
<ThemeProvider theme="purple">
|
|
177
|
+
<TurnGroup
|
|
178
|
+
cols={120}
|
|
179
|
+
turnIndex={1}
|
|
180
|
+
user={user}
|
|
181
|
+
run={run}
|
|
182
|
+
assistant={makeAssistant(turnId, "First chunk")}
|
|
183
|
+
opacity="active"
|
|
184
|
+
question={null}
|
|
185
|
+
runPhase="streaming"
|
|
186
|
+
streamPreviewRows={8}
|
|
187
|
+
streamMode="assistant-first"
|
|
188
|
+
/>
|
|
189
|
+
</ThemeProvider>,
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
await sleep();
|
|
193
|
+
let frame = harness.readOutput();
|
|
194
|
+
assert.match(frame, /first chunk/i);
|
|
195
|
+
|
|
196
|
+
// Update with more content — should render incrementally
|
|
197
|
+
harness.resetOutput();
|
|
198
|
+
harness.instance.rerender(
|
|
199
|
+
<ThemeProvider theme="purple">
|
|
200
|
+
<TurnGroup
|
|
201
|
+
cols={120}
|
|
202
|
+
turnIndex={1}
|
|
203
|
+
user={user}
|
|
204
|
+
run={run}
|
|
205
|
+
assistant={makeAssistant(turnId, "First chunk\nSecond chunk")}
|
|
206
|
+
opacity="active"
|
|
207
|
+
question={null}
|
|
208
|
+
runPhase="streaming"
|
|
209
|
+
streamPreviewRows={8}
|
|
210
|
+
streamMode="assistant-first"
|
|
211
|
+
/>
|
|
212
|
+
</ThemeProvider>,
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
await sleep();
|
|
216
|
+
frame = harness.readOutput();
|
|
217
|
+
assert.match(frame, /second chunk/i);
|
|
218
|
+
|
|
219
|
+
harness.instance.unmount();
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test("finalization with same content does not cause visual flash", async () => {
|
|
223
|
+
const turnId = 13;
|
|
224
|
+
const user = makeUser(turnId);
|
|
225
|
+
const run = makeRunningRun(turnId);
|
|
226
|
+
const content = "The response content stays the same";
|
|
227
|
+
const assistant = makeAssistant(turnId, content);
|
|
228
|
+
|
|
229
|
+
const harness = renderTurnGroup(
|
|
230
|
+
<ThemeProvider theme="purple">
|
|
231
|
+
<TurnGroup
|
|
232
|
+
cols={120}
|
|
233
|
+
turnIndex={1}
|
|
234
|
+
user={user}
|
|
235
|
+
run={run}
|
|
236
|
+
assistant={assistant}
|
|
237
|
+
opacity="active"
|
|
238
|
+
question={null}
|
|
239
|
+
runPhase="streaming"
|
|
240
|
+
streamPreviewRows={8}
|
|
241
|
+
streamMode="assistant-first"
|
|
242
|
+
/>
|
|
243
|
+
</ThemeProvider>,
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
await sleep();
|
|
247
|
+
let streamingFrame = harness.readOutput();
|
|
248
|
+
assert.match(streamingFrame, /response content stays/i);
|
|
249
|
+
|
|
250
|
+
// Transition to final with same content
|
|
251
|
+
harness.resetOutput();
|
|
252
|
+
harness.instance.rerender(
|
|
253
|
+
<ThemeProvider theme="purple">
|
|
254
|
+
<TurnGroup
|
|
255
|
+
cols={120}
|
|
256
|
+
turnIndex={1}
|
|
257
|
+
user={user}
|
|
258
|
+
run={{ ...run, status: "completed", durationMs: 800 }}
|
|
259
|
+
assistant={assistant}
|
|
260
|
+
opacity="active"
|
|
261
|
+
question={null}
|
|
262
|
+
runPhase="final"
|
|
263
|
+
streamPreviewRows={8}
|
|
264
|
+
streamMode="assistant-first"
|
|
265
|
+
/>
|
|
266
|
+
</ThemeProvider>,
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
await sleep();
|
|
270
|
+
let finalFrame = harness.readOutput();
|
|
271
|
+
// Content should still be present — no flash/disappearance
|
|
272
|
+
assert.match(finalFrame, /response content stays/i);
|
|
273
|
+
assert.match(finalFrame, /Codexa/);
|
|
274
|
+
|
|
275
|
+
harness.instance.unmount();
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test("assistant output 'Codexa' label is indented by transcriptContentIndent", async () => {
|
|
279
|
+
const turnId = 100;
|
|
280
|
+
const user = makeUser(turnId);
|
|
281
|
+
const run = makeRunningRun(turnId);
|
|
282
|
+
const assistant = makeAssistant(turnId, "Indented response");
|
|
283
|
+
|
|
284
|
+
const harness = renderTurnGroup(
|
|
285
|
+
<ThemeProvider theme="purple">
|
|
286
|
+
<TurnGroup
|
|
287
|
+
cols={120}
|
|
288
|
+
turnIndex={1}
|
|
289
|
+
user={user}
|
|
290
|
+
run={run}
|
|
291
|
+
assistant={assistant}
|
|
292
|
+
opacity="active"
|
|
293
|
+
question={null}
|
|
294
|
+
runPhase="streaming"
|
|
295
|
+
streamPreviewRows={8}
|
|
296
|
+
streamMode="assistant-first"
|
|
297
|
+
/>
|
|
298
|
+
</ThemeProvider>,
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
await sleep();
|
|
302
|
+
const frame = harness.readOutput();
|
|
303
|
+
const lines = frame.split("\n");
|
|
304
|
+
const codexaLine = lines.find((l) => l.includes("Codexa"));
|
|
305
|
+
assert.ok(codexaLine, "expected Codexa label");
|
|
306
|
+
// Check that the line starts with exactly 4 spaces (transcriptContentIndent = 4)
|
|
307
|
+
assert.match(codexaLine, /^ Codexa/);
|
|
308
|
+
|
|
309
|
+
harness.instance.unmount();
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
test("snaps cleanly from streaming cursor view to completion view", async () => {
|
|
314
|
+
const turnId = 11;
|
|
315
|
+
const user = makeUser(turnId);
|
|
316
|
+
const running = makeRunningRun(turnId);
|
|
317
|
+
const assistant = makeAssistant(turnId, "Final response text");
|
|
318
|
+
|
|
319
|
+
const harness = renderTurnGroup(
|
|
320
|
+
<ThemeProvider theme="purple">
|
|
321
|
+
<TurnGroup
|
|
322
|
+
cols={120}
|
|
323
|
+
turnIndex={2}
|
|
324
|
+
user={user}
|
|
325
|
+
run={running}
|
|
326
|
+
assistant={assistant}
|
|
327
|
+
opacity="active"
|
|
328
|
+
question={null}
|
|
329
|
+
runPhase="streaming"
|
|
330
|
+
streamPreviewRows={6}
|
|
331
|
+
streamMode="assistant-first"
|
|
332
|
+
/>
|
|
333
|
+
</ThemeProvider>,
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
await sleep();
|
|
337
|
+
let frame = harness.readOutput();
|
|
338
|
+
assert.match(frame, /final response text/i);
|
|
339
|
+
assert.match(frame, /▌/);
|
|
340
|
+
|
|
341
|
+
harness.resetOutput();
|
|
342
|
+
harness.instance.rerender(
|
|
343
|
+
<ThemeProvider theme="purple">
|
|
344
|
+
<TurnGroup
|
|
345
|
+
cols={120}
|
|
346
|
+
turnIndex={2}
|
|
347
|
+
user={user}
|
|
348
|
+
run={{ ...running, status: "completed", durationMs: 1200 }}
|
|
349
|
+
assistant={assistant}
|
|
350
|
+
opacity="active"
|
|
351
|
+
question={null}
|
|
352
|
+
runPhase="final"
|
|
353
|
+
streamPreviewRows={6}
|
|
354
|
+
streamMode="assistant-first"
|
|
355
|
+
/>
|
|
356
|
+
</ThemeProvider>,
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
await sleep();
|
|
360
|
+
frame = harness.readOutput();
|
|
361
|
+
assert.match(frame, /final response text/i);
|
|
362
|
+
assert.doesNotMatch(frame, /▌/);
|
|
363
|
+
|
|
364
|
+
harness.instance.unmount();
|
|
365
|
+
});
|