@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
|
@@ -3,19 +3,41 @@ import { normalizeWorkspaceRoot } from "./workspaceRoot.js";
|
|
|
3
3
|
|
|
4
4
|
type PathStyle = "windows" | "posix";
|
|
5
5
|
|
|
6
|
+
export function normalizeDiagnosticPath(filePath: string): string {
|
|
7
|
+
let pathPart = filePath;
|
|
8
|
+
let drivePrefix = "";
|
|
9
|
+
|
|
10
|
+
if (/^[A-Za-z]:[\\/]/.test(filePath)) {
|
|
11
|
+
drivePrefix = filePath.slice(0, 2);
|
|
12
|
+
pathPart = filePath.slice(2);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Robustly strip trailing colon-separated line/column numbers.
|
|
16
|
+
// Handles:
|
|
17
|
+
// path/to/file.rs:109:12
|
|
18
|
+
// path/to/file.rs:109
|
|
19
|
+
// C:\path\to\file.rs:12:5
|
|
20
|
+
const diagnosticSuffixRegex = /:(\d+)(?::(\d+))?$/;
|
|
21
|
+
pathPart = pathPart.replace(diagnosticSuffixRegex, "");
|
|
22
|
+
|
|
23
|
+
return drivePrefix + pathPart;
|
|
24
|
+
}
|
|
25
|
+
|
|
6
26
|
export interface WorkspacePathViolation {
|
|
7
27
|
rawPath: string;
|
|
8
28
|
normalizedPath: string;
|
|
9
29
|
}
|
|
10
30
|
|
|
11
31
|
const QUOTED_ABSOLUTE_PATH_PATTERN =
|
|
12
|
-
/(["'`])((?:[A-Za-z]:[\\/]|\\\\[^\\/\r\n]+[\\/][^\\/\r\n]+[\\/]
|
|
32
|
+
/(["'`])((?:[A-Za-z]:[\\/]|\\\\[^\\/\r\n]+[\\/][^\\/\r\n]+[\\/]|\/|~\/)[^"'`\r\n]+?)\1/g;
|
|
13
33
|
const QUOTED_RELATIVE_PATH_PATTERN = /(["'`])((?:\.\.?[\\/])[^"'`\r\n]+?)\1/g;
|
|
14
34
|
const WINDOWS_DRIVE_PATH_PATTERN = /(?:^|[\s([{\],;=])([A-Za-z]:[\\/][^\s"'`<>|]+)/g;
|
|
15
35
|
const WINDOWS_UNC_PATH_PATTERN =
|
|
16
36
|
/(?:^|[\s([{\],;=])(\\\\[^\\/\s"'`<>|]+[\\/][^\\/\s"'`<>|]+(?:[\\/][^\s"'`<>|]+)*)/g;
|
|
17
|
-
const POSIX_ABSOLUTE_PATH_PATTERN = /(?:^|[\s([{\],;=])(
|
|
37
|
+
const POSIX_ABSOLUTE_PATH_PATTERN = /(?:^|[\s([{\],;=])((?:\/|~\/)(?:[^\/\s"'`<>|]+\/)+[^\s"'`<>|]+)/g;
|
|
18
38
|
const RELATIVE_PATH_PATTERN = /(?:^|[\s([{\],;=])((?:\.\.?[\\/])(?:[^\s"'`<>|]+(?:[\\/][^\s"'`<>|]+)*))/g;
|
|
39
|
+
const RUST_RELATIVE_DIAGNOSTIC_PATH_PATTERN =
|
|
40
|
+
/(?:^|[\s([{\],;=])((?:[A-Za-z0-9_.-]+[\\/])+[A-Za-z0-9_.-]+\.rs(?::\d+(?::\d+)?)?)/g;
|
|
19
41
|
const TRAILING_PUNCTUATION_PATTERN = /[),.;:\]}]+$/;
|
|
20
42
|
const DIRECTORY_NAVIGATION_PATTERN = /(?:^|[;&\n]|&&|\|\|)\s*(cd|pushd|set-location)\b/i;
|
|
21
43
|
|
|
@@ -24,7 +46,7 @@ function detectPathStyle(pathValue: string): PathStyle | null {
|
|
|
24
46
|
return "windows";
|
|
25
47
|
}
|
|
26
48
|
|
|
27
|
-
if (pathValue.startsWith("/")) {
|
|
49
|
+
if (pathValue.startsWith("/") || pathValue.startsWith("~/")) {
|
|
28
50
|
return "posix";
|
|
29
51
|
}
|
|
30
52
|
|
|
@@ -47,6 +69,42 @@ function isExplicitRelativePath(pathValue: string): boolean {
|
|
|
47
69
|
return /^(?:\.\.?[\\/])/.test(pathValue);
|
|
48
70
|
}
|
|
49
71
|
|
|
72
|
+
function hasRustRelativeDiagnosticShape(pathValue: string): boolean {
|
|
73
|
+
return /^(?:[A-Za-z0-9_.-]+[\\/])+[A-Za-z0-9_.-]+\.rs(?::\d+(?::\d+)?)?$/.test(pathValue);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function normalizedPathSegments(pathValue: string): string[] {
|
|
77
|
+
return normalizeDiagnosticPath(pathValue)
|
|
78
|
+
.replace(/\\/g, "/")
|
|
79
|
+
.split("/")
|
|
80
|
+
.filter(Boolean)
|
|
81
|
+
.map((segment) => segment.toLowerCase());
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function isSkippedExternalDependencyPath(pathValue: string): boolean {
|
|
85
|
+
const segments = normalizedPathSegments(pathValue);
|
|
86
|
+
|
|
87
|
+
for (let i = 0; i < segments.length; i++) {
|
|
88
|
+
const segment = segments[i];
|
|
89
|
+
const next = segments[i + 1];
|
|
90
|
+
const afterNext = segments[i + 2];
|
|
91
|
+
|
|
92
|
+
if (segment === ".cargo" && next === "registry") return true;
|
|
93
|
+
if (segment === ".cargo" && next === "git" && afterNext === "checkouts") return true;
|
|
94
|
+
if (
|
|
95
|
+
segment === "target" ||
|
|
96
|
+
segment === "node_modules" ||
|
|
97
|
+
segment === ".pnpm-store" ||
|
|
98
|
+
segment === ".npm" ||
|
|
99
|
+
segment === ".cache"
|
|
100
|
+
) {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
|
|
50
108
|
function normalizeAbsolutePath(pathValue: string, style: PathStyle): string {
|
|
51
109
|
const pathApi = getPathApi(style);
|
|
52
110
|
const resolved = pathApi.normalize(pathApi.resolve(pathValue));
|
|
@@ -149,7 +207,7 @@ export function extractExplicitPathReferences(text: string): string[] {
|
|
|
149
207
|
|
|
150
208
|
const addMatch = (candidate: string) => {
|
|
151
209
|
const cleaned = stripTrailingPunctuation(stripWrappingQuotes(candidate.trim()));
|
|
152
|
-
if (!cleaned || (!detectPathStyle(cleaned) && !isExplicitRelativePath(cleaned))) {
|
|
210
|
+
if (!cleaned || (!detectPathStyle(cleaned) && !isExplicitRelativePath(cleaned) && !hasRustRelativeDiagnosticShape(cleaned))) {
|
|
153
211
|
return;
|
|
154
212
|
}
|
|
155
213
|
|
|
@@ -169,6 +227,7 @@ export function extractExplicitPathReferences(text: string): string[] {
|
|
|
169
227
|
WINDOWS_UNC_PATH_PATTERN,
|
|
170
228
|
POSIX_ABSOLUTE_PATH_PATTERN,
|
|
171
229
|
RELATIVE_PATH_PATTERN,
|
|
230
|
+
RUST_RELATIVE_DIAGNOSTIC_PATH_PATTERN,
|
|
172
231
|
]) {
|
|
173
232
|
pattern.lastIndex = 0;
|
|
174
233
|
let match = pattern.exec(text);
|
|
@@ -181,24 +240,49 @@ export function extractExplicitPathReferences(text: string): string[] {
|
|
|
181
240
|
return matches;
|
|
182
241
|
}
|
|
183
242
|
|
|
243
|
+
export function formatSkippedDependencyPath(pathValue: string): string {
|
|
244
|
+
const parts = pathValue.replace(/\\/g, "/").split("/");
|
|
245
|
+
const registryIndex = parts.indexOf("registry");
|
|
246
|
+
if (registryIndex !== -1 && parts[registryIndex + 1] === "src" && parts[registryIndex + 2]?.includes("index.crates.io")) {
|
|
247
|
+
return parts.slice(registryIndex + 3).join("/");
|
|
248
|
+
}
|
|
249
|
+
const gitIndex = parts.indexOf("git");
|
|
250
|
+
if (gitIndex !== -1 && parts[gitIndex + 1] === "checkouts") {
|
|
251
|
+
return parts.slice(gitIndex + 2).join("/");
|
|
252
|
+
}
|
|
253
|
+
return parts[parts.length - 1] ?? pathValue;
|
|
254
|
+
}
|
|
255
|
+
|
|
184
256
|
export function findOutsideWorkspacePaths(
|
|
185
257
|
text: string,
|
|
186
258
|
workspaceRoot: string,
|
|
187
259
|
allowedRoots: readonly string[] = [],
|
|
188
|
-
): WorkspacePathViolation[] {
|
|
260
|
+
): { violations: WorkspacePathViolation[]; skippedExternalPaths: string[] } {
|
|
189
261
|
const violations: WorkspacePathViolation[] = [];
|
|
262
|
+
const skippedExternalPaths: string[] = [];
|
|
190
263
|
const seen = new Set<string>();
|
|
191
264
|
const workspaceStyle = detectPathStyle(workspaceRoot) ?? "windows";
|
|
192
265
|
|
|
193
266
|
for (const rawPath of extractExplicitPathReferences(text)) {
|
|
194
|
-
|
|
267
|
+
const cleanPath = normalizeDiagnosticPath(rawPath);
|
|
268
|
+
if (isSkippedExternalDependencyPath(cleanPath)) {
|
|
269
|
+
skippedExternalPaths.push(cleanPath);
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (isPathInsideAllowedRoots(cleanPath, workspaceRoot, allowedRoots)) {
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const explicitStyle = detectPathStyle(cleanPath) ?? workspaceStyle;
|
|
278
|
+
const normalizedPath = detectPathStyle(cleanPath)
|
|
279
|
+
? normalizeAbsolutePath(cleanPath, explicitStyle)
|
|
280
|
+
: resolveWorkspacePath(cleanPath, workspaceRoot);
|
|
281
|
+
if (isSkippedExternalDependencyPath(normalizedPath)) {
|
|
282
|
+
skippedExternalPaths.push(normalizedPath);
|
|
195
283
|
continue;
|
|
196
284
|
}
|
|
197
285
|
|
|
198
|
-
const explicitStyle = detectPathStyle(rawPath) ?? workspaceStyle;
|
|
199
|
-
const normalizedPath = detectPathStyle(rawPath)
|
|
200
|
-
? normalizeAbsolutePath(rawPath, explicitStyle)
|
|
201
|
-
: resolveWorkspacePath(rawPath, workspaceRoot);
|
|
202
286
|
const comparisonKey = explicitStyle === "windows" ? normalizedPath.toLowerCase() : normalizedPath;
|
|
203
287
|
|
|
204
288
|
if (seen.has(comparisonKey)) {
|
|
@@ -209,7 +293,7 @@ export function findOutsideWorkspacePaths(
|
|
|
209
293
|
violations.push({ rawPath, normalizedPath });
|
|
210
294
|
}
|
|
211
295
|
|
|
212
|
-
return violations;
|
|
296
|
+
return { violations, skippedExternalPaths };
|
|
213
297
|
}
|
|
214
298
|
|
|
215
299
|
export function containsDirectoryNavigationCommand(command: string): boolean {
|
|
@@ -248,7 +332,7 @@ export function getPromptWorkspaceGuardMessage(
|
|
|
248
332
|
workspaceRoot: string,
|
|
249
333
|
allowedRoots: readonly string[] = [],
|
|
250
334
|
): string | null {
|
|
251
|
-
const violations = findOutsideWorkspacePaths(prompt, workspaceRoot, allowedRoots);
|
|
335
|
+
const { violations } = findOutsideWorkspacePaths(prompt, workspaceRoot, allowedRoots);
|
|
252
336
|
if (violations.length === 0) {
|
|
253
337
|
return null;
|
|
254
338
|
}
|
|
@@ -274,7 +358,7 @@ export function getShellWorkspaceGuardMessage(
|
|
|
274
358
|
].join("\n");
|
|
275
359
|
}
|
|
276
360
|
|
|
277
|
-
const violations = findOutsideWorkspacePaths(command, workspaceRoot, allowedRoots);
|
|
361
|
+
const { violations } = findOutsideWorkspacePaths(command, workspaceRoot, allowedRoots);
|
|
278
362
|
if (violations.length === 0) {
|
|
279
363
|
return null;
|
|
280
364
|
}
|
|
@@ -8,12 +8,12 @@ import {
|
|
|
8
8
|
resolveRuntimeConfig,
|
|
9
9
|
type ResolvedRuntimeConfig,
|
|
10
10
|
} from "../config/runtimeConfig.js";
|
|
11
|
-
import { loadProjectInstructions, type ProjectInstructionsLoadResult } from "../core/projectInstructions.js";
|
|
11
|
+
import { loadProjectInstructions, type ProjectInstructionsLoadResult } from "../core/workspace/projectInstructions.js";
|
|
12
12
|
import { getBackendProvider } from "../core/providers/registry.js";
|
|
13
13
|
import type { BackendProvider } from "../core/providers/types.js";
|
|
14
14
|
import { isNoiseLine } from "../core/providers/codexTranscript.js";
|
|
15
15
|
import { sanitizeTerminalOutput } from "../core/terminal/terminalSanitize.js";
|
|
16
|
-
import { resolveWorkspaceRoot } from "../core/workspaceRoot.js";
|
|
16
|
+
import { resolveWorkspaceRoot } from "../core/workspace/workspaceRoot.js";
|
|
17
17
|
import type { RunToolActivity } from "../session/types.js";
|
|
18
18
|
|
|
19
19
|
// ─── Types & constants ────────────────────────────────────────────────────────
|
package/src/index.tsx
CHANGED
|
@@ -7,55 +7,35 @@ import { APP_NAME, formatTerminalTitlePath } from "./config/settings.js";
|
|
|
7
7
|
import { getTerminalCapability } from "./core/terminal/terminalCapabilities.js";
|
|
8
8
|
import * as renderDebug from "./core/perf/renderDebug.js";
|
|
9
9
|
import { MIN_VIEWPORT_COLS, MIN_VIEWPORT_ROWS } from "./ui/layout.js";
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
mode
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Resolve the real Ink class instance via Ink's internal WeakMap<stdout, Ink>.
|
|
41
|
-
* Returns null if resolution fails (e.g. different Ink version, test mocks).
|
|
42
|
-
*/
|
|
43
|
-
function resolveInkInstance(stdout: AppStdout): InkInstance | null {
|
|
44
|
-
try {
|
|
45
|
-
// Bun doesn't resolve bare subpath imports like "ink/build/instances.js"
|
|
46
|
-
// so we resolve ink's main entry first, then derive the sibling path.
|
|
47
|
-
const { createRequire } = require("node:module");
|
|
48
|
-
const req = createRequire(import.meta.url);
|
|
49
|
-
const inkMain = req.resolve("ink");
|
|
50
|
-
const instancesPath = inkMain.replace(/index\.js$/, "instances.js");
|
|
51
|
-
const instances = req(instancesPath);
|
|
52
|
-
const weakMap: WeakMap<object, InkInstance> =
|
|
53
|
-
instances.default ?? instances;
|
|
54
|
-
return weakMap.get(stdout as object) ?? null;
|
|
55
|
-
} catch {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
10
|
+
import {
|
|
11
|
+
setIntendedTerminalTitle,
|
|
12
|
+
} from "./core/terminal/terminalTitle.js";
|
|
13
|
+
import { resolveWorkspaceRoot } from "./core/workspace/workspaceRoot.js";
|
|
14
|
+
import {
|
|
15
|
+
createTerminalModeController,
|
|
16
|
+
writeTerminalControl,
|
|
17
|
+
} from "./core/terminal/terminalControl.js";
|
|
18
|
+
import { resolveInkRenderInstance, type InkRenderInstance } from "./core/terminal/inkRenderReset.js";
|
|
19
|
+
import { resetFrameLockForResize, wrapStdoutWithFrameLock } from "./core/terminal/frameLock.js";
|
|
20
|
+
|
|
21
|
+
type RenderHandle = Pick<Instance, "clear" | "cleanup" | "waitUntilExit">;
|
|
22
|
+
|
|
23
|
+
export function resolveKittyKeyboardOptions(
|
|
24
|
+
env: Record<string, string | undefined>,
|
|
25
|
+
): RenderOptions["kittyKeyboard"] | undefined {
|
|
26
|
+
// Ink's auto mode probes with CSI ? u. Kitty's response can race Ink's
|
|
27
|
+
// regular input listeners and leak into both the first frame and composer.
|
|
28
|
+
// A direct Kitty session is already known to support the protocol, so enable
|
|
29
|
+
// it without probing. Avoid protocol negotiation in all other terminals.
|
|
30
|
+
if ((env.TERM ?? "").toLowerCase() !== "xterm-kitty") {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
mode: "enabled",
|
|
36
|
+
flags: ["disambiguateEscapeCodes"],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
59
39
|
|
|
60
40
|
export interface AppStdout {
|
|
61
41
|
isTTY: boolean;
|
|
@@ -74,7 +54,7 @@ export interface StartAppDependencies {
|
|
|
74
54
|
platform: NodeJS.Platform;
|
|
75
55
|
argv: string[];
|
|
76
56
|
renderApp: (node: React.ReactElement, options?: RenderOptions) => RenderHandle;
|
|
77
|
-
resolveInkInstanceForStdout: (stdout: AppStdout) =>
|
|
57
|
+
resolveInkInstanceForStdout: (stdout: AppStdout) => InkRenderInstance | null;
|
|
78
58
|
registerExitHandler: (handler: () => void) => void;
|
|
79
59
|
}
|
|
80
60
|
|
|
@@ -112,7 +92,7 @@ export function startApp({
|
|
|
112
92
|
platform = process.platform,
|
|
113
93
|
argv = process.argv.slice(2),
|
|
114
94
|
renderApp = render,
|
|
115
|
-
resolveInkInstanceForStdout =
|
|
95
|
+
resolveInkInstanceForStdout = resolveInkRenderInstance,
|
|
116
96
|
registerExitHandler = (handler) => {
|
|
117
97
|
process.on("exit", handler);
|
|
118
98
|
},
|
|
@@ -123,7 +103,11 @@ export function startApp({
|
|
|
123
103
|
return { started: true, exitCode: 0 };
|
|
124
104
|
}
|
|
125
105
|
|
|
126
|
-
|
|
106
|
+
// Wrap stdout to implement frame locking, deduplication, and width-safe row
|
|
107
|
+
// padding via \x1b[K injection across the entire TUI.
|
|
108
|
+
const wrappedStdout = wrapStdoutWithFrameLock({ stdout, env });
|
|
109
|
+
|
|
110
|
+
const terminal = createTerminalModeController((chunk) => wrappedStdout.write(chunk));
|
|
127
111
|
const writeStdout = (chunk: string, source: string): boolean => terminal.write(chunk, source);
|
|
128
112
|
|
|
129
113
|
const writeStderr = (chunk: string, source: string): boolean => {
|
|
@@ -163,56 +147,38 @@ export function startApp({
|
|
|
163
147
|
return { started: false, exitCode: 1 };
|
|
164
148
|
}
|
|
165
149
|
const launchArgs: LaunchArgs = parsedLaunchArgs.value;
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
});
|
|
171
|
-
const stopStartupTitleGuard = startTerminalTitleStartupGuard({
|
|
172
|
-
write: (chunk) => writeStdout(chunk, "src/index.tsx:title.startupGuard"),
|
|
173
|
-
reason: "index-startup-guard",
|
|
174
|
-
intervalMs: 150,
|
|
175
|
-
durationMs: 3500,
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
// Clear the screen (viewport + scrollback) and move cursor home before Ink
|
|
179
|
-
// renders the first frame. This removes any previous terminal content so
|
|
180
|
-
// the app opens into a clean screen. We stay in the normal screen buffer
|
|
181
|
-
// (no \x1b[?1049h) to preserve mouse text selection after exit.
|
|
182
|
-
// Skipped when --no-clear or CODEXA_NO_CLEAR=1 is set.
|
|
183
|
-
// NOTE: Mouse reporting is NOT enabled here — managed solely by app.tsx.
|
|
184
|
-
traceTerminalClear("src/index.tsx:startup", { mode: "transcript" });
|
|
185
|
-
performStartupClear({
|
|
186
|
-
write: (c) => writeStdout(c, "src/index.tsx:startup"),
|
|
187
|
-
noClear: launchArgs.noClear,
|
|
188
|
-
env,
|
|
189
|
-
});
|
|
190
|
-
const startupWorkspaceRoot = resolveWorkspaceRoot();
|
|
150
|
+
// Main chat starts in the normal screen buffer so the startup frame and later
|
|
151
|
+
// transcript rows are owned by native terminal scrollback. Overlay screens
|
|
152
|
+
// enter alternate-screen mode from app.tsx only while an overlay is active.
|
|
153
|
+
const startupWorkspaceRoot = resolveWorkspaceRoot();
|
|
191
154
|
const startupSettings = loadSettings();
|
|
192
155
|
const startupTitle =
|
|
193
156
|
formatTerminalTitlePath(startupWorkspaceRoot, startupSettings.ui.terminalTitleMode) || APP_NAME;
|
|
194
|
-
setIntendedTerminalTitle(startupTitle, {
|
|
195
|
-
force: true,
|
|
196
|
-
reason: "startup-title",
|
|
197
|
-
write: (chunk) => writeStdout(chunk, "src/index.tsx:startup.title"),
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
reason: "startup-title-ready",
|
|
202
|
-
});
|
|
203
|
-
terminal.setBracketedPaste(true, "src/index.tsx:startup.bracketedPaste");
|
|
204
|
-
|
|
157
|
+
setIntendedTerminalTitle(startupTitle, {
|
|
158
|
+
force: true,
|
|
159
|
+
reason: "startup-title",
|
|
160
|
+
write: (chunk) => writeStdout(chunk, "src/index.tsx:startup.title"),
|
|
161
|
+
});
|
|
162
|
+
terminal.setBracketedPaste(true, "src/index.tsx:startup.bracketedPaste");
|
|
163
|
+
|
|
205
164
|
let cleanupDone = false;
|
|
206
165
|
let repaintArmed = false;
|
|
207
166
|
let renderHandle: RenderHandle | null = null;
|
|
208
|
-
let inkInstance:
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
167
|
+
let inkInstance: InkRenderInstance | null = null;
|
|
168
|
+
let previousResizeCols = stdout.columns;
|
|
169
|
+
let previousResizeRows = stdout.rows;
|
|
170
|
+
|
|
171
|
+
const onResize = () => {
|
|
172
|
+
resetFrameLockForResize(wrappedStdout);
|
|
173
|
+
renderDebug.traceEvent("terminal", "resize", {
|
|
174
|
+
previousCols: previousResizeCols,
|
|
175
|
+
previousRows: previousResizeRows,
|
|
212
176
|
cols: stdout.columns,
|
|
213
177
|
rows: stdout.rows,
|
|
214
178
|
invalid: hasInvalidRestoreDimensions(stdout),
|
|
215
179
|
});
|
|
180
|
+
previousResizeCols = stdout.columns;
|
|
181
|
+
previousResizeRows = stdout.rows;
|
|
216
182
|
|
|
217
183
|
if (hasInvalidRestoreDimensions(stdout)) {
|
|
218
184
|
// Transient invalid dimensions (e.g. during maximize/restore on
|
|
@@ -234,12 +200,12 @@ export function startApp({
|
|
|
234
200
|
const cleanup = () => {
|
|
235
201
|
if (cleanupDone) return;
|
|
236
202
|
cleanupDone = true;
|
|
237
|
-
|
|
238
|
-
stdout.off("resize", onResize);
|
|
203
|
+
stdout.off("resize", onResize);
|
|
239
204
|
renderHandle?.cleanup();
|
|
240
205
|
// Restore terminal state: disable mouse reporting and bracketed paste.
|
|
241
206
|
terminal.setMouseReporting(false, "src/index.tsx:cleanup.mouse");
|
|
242
207
|
terminal.setBracketedPaste(false, "src/index.tsx:cleanup.bracketedPaste");
|
|
208
|
+
terminal.setAlternateScreen(false, "src/index.tsx:cleanup.alternateScreen");
|
|
243
209
|
terminal.resetModes();
|
|
244
210
|
activeRoot = null;
|
|
245
211
|
};
|
|
@@ -269,13 +235,15 @@ export function startApp({
|
|
|
269
235
|
process.on("uncaughtException", handleFatal);
|
|
270
236
|
process.on("unhandledRejection", handleFatal);
|
|
271
237
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
238
|
+
const kittyKeyboard = resolveKittyKeyboardOptions(env);
|
|
239
|
+
renderHandle = renderApp(<App launchArgs={launchArgs} />, {
|
|
240
|
+
...(kittyKeyboard ? { kittyKeyboard } : {}),
|
|
241
|
+
stdout: wrappedStdout as any,
|
|
242
|
+
});
|
|
275
243
|
|
|
276
244
|
// Resolve the real Ink class instance to get access to lastOutput,
|
|
277
245
|
// onRender, calculateLayout, etc. Gracefully degrades to null in tests.
|
|
278
|
-
inkInstance = resolveInkInstanceForStdout(
|
|
246
|
+
inkInstance = resolveInkInstanceForStdout(wrappedStdout);
|
|
279
247
|
|
|
280
248
|
// Remove Ink's own resize handler so the app is the sole resize handler.
|
|
281
249
|
// This eliminates the race where Ink's resized() fires alongside our
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
upsertRunToolActivity,
|
|
19
19
|
type UIStateAction,
|
|
20
20
|
} from "./chatLifecycle.js";
|
|
21
|
-
import type { RunFileActivity } from "../core/workspaceActivity.js";
|
|
21
|
+
import type { RunFileActivity } from "../core/workspace/workspaceActivity.js";
|
|
22
22
|
import type { RunToolActivity } from "./types.js";
|
|
23
23
|
import type { LiveRenderUpdate } from "./liveRenderScheduler.js";
|
|
24
24
|
import * as renderDebug from "../core/perf/renderDebug.js";
|
|
@@ -47,7 +47,7 @@ export type SessionAction =
|
|
|
47
47
|
| { type: "SUBMIT_PROMPT_RUN"; historyValue?: string; events: TimelineEvent[]; turnId: number; runId: number }
|
|
48
48
|
| { type: "HISTORY_UP" }
|
|
49
49
|
| { type: "HISTORY_DOWN" }
|
|
50
|
-
| { type: "CLEAR_TRANSCRIPT" }
|
|
50
|
+
| { type: "CLEAR_TRANSCRIPT"; seedEvents?: TimelineEvent[] }
|
|
51
51
|
| { type: "SET_ACTIVE_EVENTS"; events: TimelineEvent[] }
|
|
52
52
|
| { type: "RUN_APPEND_ACTIVITY"; runId: number; activity: RunFileActivity[] }
|
|
53
53
|
| { type: "RUN_APPLY_PROGRESS_UPDATES"; runId: number; updates: BackendProgressUpdate[] }
|
|
@@ -98,9 +98,9 @@ export type SessionAction =
|
|
|
98
98
|
|
|
99
99
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
100
100
|
|
|
101
|
-
export function createInitialSessionState(): SessionState {
|
|
101
|
+
export function createInitialSessionState(options: { staticEvents?: TimelineEvent[] } = {}): SessionState {
|
|
102
102
|
return {
|
|
103
|
-
staticEvents: [],
|
|
103
|
+
staticEvents: options.staticEvents ?? [],
|
|
104
104
|
activeEvents: [],
|
|
105
105
|
uiState: { kind: "IDLE" },
|
|
106
106
|
externalCliStatus: "idle",
|
|
@@ -327,11 +327,12 @@ export function reduceSessionState(state: SessionState, action: SessionAction):
|
|
|
327
327
|
renderDebug.traceEvent("transcript", "clear", {
|
|
328
328
|
previousStaticEventsLength: state.staticEvents.length,
|
|
329
329
|
previousActiveEventsLength: state.activeEvents.length,
|
|
330
|
+
seedEventsLength: action.seedEvents?.length ?? 0,
|
|
330
331
|
uiStateKind: state.uiState.kind,
|
|
331
332
|
});
|
|
332
333
|
return {
|
|
333
334
|
...state,
|
|
334
|
-
staticEvents: [],
|
|
335
|
+
staticEvents: action.seedEvents ?? [],
|
|
335
336
|
activeEvents: [],
|
|
336
337
|
uiState: { kind: "IDLE" },
|
|
337
338
|
clearCount: state.clearCount + 1,
|
|
@@ -694,8 +695,10 @@ export function reduceSessionState(state: SessionState, action: SessionAction):
|
|
|
694
695
|
|
|
695
696
|
// ─── Hook ────────────────────────────────────────────────────────────────────
|
|
696
697
|
|
|
697
|
-
export function useAppSessionState() {
|
|
698
|
-
const [state, setState] = useState<SessionState>(
|
|
698
|
+
export function useAppSessionState(initialStaticEvents?: () => TimelineEvent[]) {
|
|
699
|
+
const [state, setState] = useState<SessionState>(() =>
|
|
700
|
+
createInitialSessionState({ staticEvents: initialStaticEvents?.() ?? [] }),
|
|
701
|
+
);
|
|
699
702
|
const queueRef = useRef<SessionAction[]>([]);
|
|
700
703
|
const scheduledRef = useRef(false);
|
|
701
704
|
|
|
@@ -2,7 +2,7 @@ import { MAX_CHAT_LINES } from "../config/settings.js";
|
|
|
2
2
|
import type { AvailableBackend } from "../config/settings.js";
|
|
3
3
|
import type { ResolvedRuntimeConfig } from "../config/runtimeConfig.js";
|
|
4
4
|
import type { BackendProgressUpdate } from "../core/providers/types.js";
|
|
5
|
-
import { summarizeRunActivity, type RunFileActivity } from "../core/workspaceActivity.js";
|
|
5
|
+
import { summarizeRunActivity, type RunFileActivity } from "../core/workspace/workspaceActivity.js";
|
|
6
6
|
import * as renderDebug from "../core/perf/renderDebug.js";
|
|
7
7
|
import type {
|
|
8
8
|
RunEvent,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BackendProgressUpdate } from "../core/providers/types.js";
|
|
2
|
-
import type { RunFileActivity } from "../core/workspaceActivity.js";
|
|
2
|
+
import type { RunFileActivity } from "../core/workspace/workspaceActivity.js";
|
|
3
3
|
import type { RunToolActivity } from "./types.js";
|
|
4
4
|
import * as renderDebug from "../core/perf/renderDebug.js";
|
|
5
5
|
|
package/src/session/types.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type {
|
|
|
3
3
|
AvailableBackend,
|
|
4
4
|
} from "../config/settings.js";
|
|
5
5
|
import type { ResolvedRuntimeConfig } from "../config/runtimeConfig.js";
|
|
6
|
-
import type { RunActivitySummary, RunFileActivity } from "../core/workspaceActivity.js";
|
|
6
|
+
import type { RunActivitySummary, RunFileActivity } from "../core/workspace/workspaceActivity.js";
|
|
7
7
|
|
|
8
8
|
// ─── Screen routing ──────────────────────────────────────────────────────────
|
|
9
9
|
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
1
4
|
import { resolveRuntimeConfig, type RuntimeConfig, DEFAULT_RUNTIME_CONFIG, type ResolvedRuntimeConfig } from "../config/runtimeConfig.js";
|
|
5
|
+
import type { ProviderId } from "../core/providerLauncher/types.js";
|
|
6
|
+
import { resetAnthropicRouteValidationCacheForTests } from "../core/providerRuntime/anthropic.js";
|
|
7
|
+
import { resetAntigravityRouteValidationCacheForTests } from "../core/providerRuntime/antigravity.js";
|
|
8
|
+
import type { ProviderModel } from "../core/providerRuntime/types.js";
|
|
2
9
|
|
|
3
10
|
export function makeResolvedRuntime(overrides: Partial<RuntimeConfig> = {}): ResolvedRuntimeConfig {
|
|
4
11
|
return resolveRuntimeConfig({
|
|
@@ -12,3 +19,86 @@ export function makeResolvedRuntime(overrides: Partial<RuntimeConfig> = {}): Res
|
|
|
12
19
|
}
|
|
13
20
|
|
|
14
21
|
export const TEST_RUNTIME = makeResolvedRuntime();
|
|
22
|
+
|
|
23
|
+
function agyModel(id: string, label: string): ProviderModel {
|
|
24
|
+
return {
|
|
25
|
+
id,
|
|
26
|
+
modelId: id,
|
|
27
|
+
label,
|
|
28
|
+
description: null,
|
|
29
|
+
defaultReasoningLevel: null,
|
|
30
|
+
supportedReasoningLevels: null,
|
|
31
|
+
source: "discovered",
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The catalog `agy models` reports on a configured machine. Antigravity resolves
|
|
37
|
+
* its status, backend kind, and model labels from whatever discovery last found,
|
|
38
|
+
* so tests that assert on any of those need a catalog seeded rather than
|
|
39
|
+
* whatever happens to be installed.
|
|
40
|
+
*/
|
|
41
|
+
export const ANTIGRAVITY_FIXTURE_MODELS: readonly ProviderModel[] = [
|
|
42
|
+
agyModel("gemini-3.5-flash", "Gemini 3.5 Flash"),
|
|
43
|
+
agyModel("gemini-3.1-pro", "Gemini 3.1 Pro"),
|
|
44
|
+
agyModel("claude-sonnet-4-6", "Claude Sonnet 4.6"),
|
|
45
|
+
agyModel("claude-sonnet-4-6-think", "Claude Sonnet 4.6 (Thinking)"),
|
|
46
|
+
agyModel("gpt-oss-120b", "GPT-OSS 120B"),
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Drops every provider's in-memory discovery result.
|
|
51
|
+
*
|
|
52
|
+
* Bun shares the module registry across test files, so a catalog discovered by
|
|
53
|
+
* one test (some probe the real `claude` / `agy` CLIs) otherwise survives into
|
|
54
|
+
* the next and takes precedence over both the cache and the fallback list —
|
|
55
|
+
* making assertions depend on file order and on what is installed locally.
|
|
56
|
+
*/
|
|
57
|
+
export function resetProviderDiscoveryState(): void {
|
|
58
|
+
resetAntigravityRouteValidationCacheForTests();
|
|
59
|
+
resetAnthropicRouteValidationCacheForTests();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Runs `fn` with HOME pointed at a throwaway directory holding only the provider
|
|
64
|
+
* model cache built from `seed`.
|
|
65
|
+
*
|
|
66
|
+
* Provider discovery reads `~/.codexa-model-cache.json`, so a test that calls it
|
|
67
|
+
* without this is asserting against the developer's own machine — it passes with
|
|
68
|
+
* a warm cache and fails on a cold one (or in CI). HOME is read per call (see
|
|
69
|
+
* `getProviderModelCacheFile`), so redirecting the env var here is enough.
|
|
70
|
+
*
|
|
71
|
+
* Antigravity's in-memory discovery is reset around the body as well: Bun shares
|
|
72
|
+
* the module registry across test files, so a catalog discovered by one file
|
|
73
|
+
* otherwise leaks into the next and takes precedence over the seeded cache,
|
|
74
|
+
* making results depend on file order. Seed exactly what the assertions need.
|
|
75
|
+
*/
|
|
76
|
+
export function withSeededModelCache<T>(
|
|
77
|
+
seed: Partial<Record<ProviderId, readonly ProviderModel[]>>,
|
|
78
|
+
fn: () => T,
|
|
79
|
+
): T {
|
|
80
|
+
const tempHome = mkdtempSync(join(tmpdir(), "codexa-test-home-"));
|
|
81
|
+
const previousHome = process.env.HOME;
|
|
82
|
+
const previousUserProfile = process.env.USERPROFILE;
|
|
83
|
+
try {
|
|
84
|
+
process.env.HOME = tempHome;
|
|
85
|
+
delete process.env.USERPROFILE;
|
|
86
|
+
const providers = Object.fromEntries(
|
|
87
|
+
Object.entries(seed).map(([providerId, models]) => [providerId, { discoveredAt: 1, models }]),
|
|
88
|
+
);
|
|
89
|
+
writeFileSync(
|
|
90
|
+
join(tempHome, ".codexa-model-cache.json"),
|
|
91
|
+
`${JSON.stringify({ version: 1, providers }, null, 2)}\n`,
|
|
92
|
+
"utf8",
|
|
93
|
+
);
|
|
94
|
+
resetProviderDiscoveryState();
|
|
95
|
+
return fn();
|
|
96
|
+
} finally {
|
|
97
|
+
if (previousHome === undefined) delete process.env.HOME;
|
|
98
|
+
else process.env.HOME = previousHome;
|
|
99
|
+
if (previousUserProfile === undefined) delete process.env.USERPROFILE;
|
|
100
|
+
else process.env.USERPROFILE = previousUserProfile;
|
|
101
|
+
rmSync(tempHome, { recursive: true, force: true });
|
|
102
|
+
resetProviderDiscoveryState();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
|
-
import { useTheme } from "
|
|
3
|
+
import { useTheme } from "../theme.js";
|
|
4
4
|
|
|
5
5
|
// ─── WaveBar ─────────────────────────────────────────────────────────────────
|
|
6
6
|
// Audio-visualizer style wave. Renders as a single <Text> node to minimise
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useState, useEffect } from "react";
|
|
2
2
|
import { Text } from "ink";
|
|
3
|
-
import type { UIState, ExternalCliStatus } from "
|
|
4
|
-
import { useTheme } from "
|
|
3
|
+
import type { UIState, ExternalCliStatus } from "../../session/types.js";
|
|
4
|
+
import { useTheme } from "../theme.js";
|
|
5
5
|
|
|
6
6
|
const SPINNER_FRAMES = ["?", "?", "?", "?", "?", "?", "?", "?", "?", "?"];
|
|
7
7
|
const STREAMING_FRAMES = ["?", "?", "?", "?"];
|
|
@@ -36,7 +36,7 @@ export function ActivityIndicator({ uiState, externalCliStatus = "idle" }: Activ
|
|
|
36
36
|
let bold = false;
|
|
37
37
|
|
|
38
38
|
if (isError) {
|
|
39
|
-
glyph = "�";
|
|
39
|
+
glyph = "�";
|
|
40
40
|
color = theme.error;
|
|
41
41
|
bold = true;
|
|
42
42
|
} else if (isAction) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React, { useEffect, useState } from "react";
|
|
2
2
|
import { Text } from "ink";
|
|
3
|
-
import * as renderDebug from "
|
|
4
|
-
import { useTheme } from "
|
|
5
|
-
import { sanitizeTerminalOutput } from "
|
|
3
|
+
import * as renderDebug from "../../core/perf/renderDebug.js";
|
|
4
|
+
import { useTheme } from "../theme.js";
|
|
5
|
+
import { sanitizeTerminalOutput } from "../../core/terminal/terminalSanitize.js";
|
|
6
6
|
import { BUSY_STATUS_FRAME_MS, BUSY_STATUS_FRAMES, getBusyStatusFrame } from "./busyStatusAnimation.js";
|
|
7
7
|
|
|
8
8
|
interface AnimatedStatusTextProps {
|