@golba98/codexa 1.0.3 → 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 +17 -18
- package/bin/codexa.js +62 -144
- package/package.json +4 -3
- package/src/app.tsx +533 -275
- package/src/commands/handler.ts +2 -2
- 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/perf/renderDebug.ts +10 -6
- 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/{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 +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 +1 -1
- package/src/ui/AppShell.tsx +672 -706
- package/src/ui/AttachmentImportPanel.tsx +2 -2
- package/src/ui/BottomComposer.tsx +145 -144
- package/src/ui/ModelPickerScreen.tsx +216 -36
- package/src/ui/ModelReasoningPicker.tsx +1 -1
- package/src/ui/PlanReviewPanel.tsx +1 -1
- package/src/ui/ProviderPicker.tsx +735 -321
- package/src/ui/RuntimeStatusBar.tsx +108 -0
- package/src/ui/SelectionPanel.tsx +4 -0
- package/src/ui/SettingsPanel.tsx +1 -1
- package/src/ui/TextEntryPanel.tsx +1 -1
- package/src/ui/Timeline.tsx +1619 -1470
- package/src/ui/TopHeader.tsx +46 -30
- package/src/ui/TranscriptShell.tsx +322 -0
- package/src/ui/TurnGroup.tsx +2 -2
- package/src/ui/UpdateAvailableCard.tsx +3 -3
- package/src/ui/UpdatePromptPanel.tsx +16 -22
- package/src/ui/layout.ts +298 -24
- package/src/ui/layoutListWindow.ts +145 -0
- package/src/ui/logoVariants.ts +4 -8
- package/src/ui/runtimeDisplay.ts +15 -3
- package/src/ui/textLayout.ts +15 -4
- package/src/ui/timelineMeasure.ts +194 -122
- package/src/core/codex.ts +0 -124
- 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
|
@@ -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,19 +7,16 @@ 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
|
-
writeTerminalControl,
|
|
21
|
-
} from "./core/terminal/terminalControl.js";
|
|
22
|
-
import { performStartupClear } from "./core/terminal/startupClear.js";
|
|
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";
|
|
23
20
|
|
|
24
21
|
type RenderHandle = Pick<Instance, "clear" | "cleanup" | "waitUntilExit">;
|
|
25
22
|
const KITTY_KEYBOARD_OPTIONS: RenderOptions["kittyKeyboard"] = {
|
|
@@ -27,36 +24,6 @@ const KITTY_KEYBOARD_OPTIONS: RenderOptions["kittyKeyboard"] = {
|
|
|
27
24
|
flags: ["disambiguateEscapeCodes"],
|
|
28
25
|
};
|
|
29
26
|
|
|
30
|
-
/**
|
|
31
|
-
* Typed subset of the internal Ink class instance we access to disable Ink's
|
|
32
|
-
* built-in resize listener. Post-startup repainting is driven by React layout
|
|
33
|
-
* state, not by forcing Ink's private render buffers.
|
|
34
|
-
*/
|
|
35
|
-
interface InkInstance {
|
|
36
|
-
unsubscribeResize?: () => void;
|
|
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
|
-
}
|
|
59
|
-
|
|
60
27
|
export interface AppStdout {
|
|
61
28
|
isTTY: boolean;
|
|
62
29
|
write: (chunk: string) => boolean;
|
|
@@ -74,7 +41,7 @@ export interface StartAppDependencies {
|
|
|
74
41
|
platform: NodeJS.Platform;
|
|
75
42
|
argv: string[];
|
|
76
43
|
renderApp: (node: React.ReactElement, options?: RenderOptions) => RenderHandle;
|
|
77
|
-
resolveInkInstanceForStdout: (stdout: AppStdout) =>
|
|
44
|
+
resolveInkInstanceForStdout: (stdout: AppStdout) => InkRenderInstance | null;
|
|
78
45
|
registerExitHandler: (handler: () => void) => void;
|
|
79
46
|
}
|
|
80
47
|
|
|
@@ -112,7 +79,7 @@ export function startApp({
|
|
|
112
79
|
platform = process.platform,
|
|
113
80
|
argv = process.argv.slice(2),
|
|
114
81
|
renderApp = render,
|
|
115
|
-
resolveInkInstanceForStdout =
|
|
82
|
+
resolveInkInstanceForStdout = resolveInkRenderInstance,
|
|
116
83
|
registerExitHandler = (handler) => {
|
|
117
84
|
process.on("exit", handler);
|
|
118
85
|
},
|
|
@@ -123,7 +90,11 @@ export function startApp({
|
|
|
123
90
|
return { started: true, exitCode: 0 };
|
|
124
91
|
}
|
|
125
92
|
|
|
126
|
-
|
|
93
|
+
// Wrap stdout to implement frame locking, deduplication, and width-safe row
|
|
94
|
+
// padding via \x1b[K injection across the entire TUI.
|
|
95
|
+
const wrappedStdout = wrapStdoutWithFrameLock({ stdout, env });
|
|
96
|
+
|
|
97
|
+
const terminal = createTerminalModeController((chunk) => wrappedStdout.write(chunk));
|
|
127
98
|
const writeStdout = (chunk: string, source: string): boolean => terminal.write(chunk, source);
|
|
128
99
|
|
|
129
100
|
const writeStderr = (chunk: string, source: string): boolean => {
|
|
@@ -163,56 +134,38 @@ export function startApp({
|
|
|
163
134
|
return { started: false, exitCode: 1 };
|
|
164
135
|
}
|
|
165
136
|
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();
|
|
137
|
+
// Main chat starts in the normal screen buffer so the startup frame and later
|
|
138
|
+
// transcript rows are owned by native terminal scrollback. Overlay screens
|
|
139
|
+
// enter alternate-screen mode from app.tsx only while an overlay is active.
|
|
140
|
+
const startupWorkspaceRoot = resolveWorkspaceRoot();
|
|
191
141
|
const startupSettings = loadSettings();
|
|
192
142
|
const startupTitle =
|
|
193
143
|
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
|
-
|
|
144
|
+
setIntendedTerminalTitle(startupTitle, {
|
|
145
|
+
force: true,
|
|
146
|
+
reason: "startup-title",
|
|
147
|
+
write: (chunk) => writeStdout(chunk, "src/index.tsx:startup.title"),
|
|
148
|
+
});
|
|
149
|
+
terminal.setBracketedPaste(true, "src/index.tsx:startup.bracketedPaste");
|
|
150
|
+
|
|
205
151
|
let cleanupDone = false;
|
|
206
152
|
let repaintArmed = false;
|
|
207
153
|
let renderHandle: RenderHandle | null = null;
|
|
208
|
-
let inkInstance:
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
154
|
+
let inkInstance: InkRenderInstance | null = null;
|
|
155
|
+
let previousResizeCols = stdout.columns;
|
|
156
|
+
let previousResizeRows = stdout.rows;
|
|
157
|
+
|
|
158
|
+
const onResize = () => {
|
|
159
|
+
resetFrameLockForResize(wrappedStdout);
|
|
160
|
+
renderDebug.traceEvent("terminal", "resize", {
|
|
161
|
+
previousCols: previousResizeCols,
|
|
162
|
+
previousRows: previousResizeRows,
|
|
212
163
|
cols: stdout.columns,
|
|
213
164
|
rows: stdout.rows,
|
|
214
165
|
invalid: hasInvalidRestoreDimensions(stdout),
|
|
215
166
|
});
|
|
167
|
+
previousResizeCols = stdout.columns;
|
|
168
|
+
previousResizeRows = stdout.rows;
|
|
216
169
|
|
|
217
170
|
if (hasInvalidRestoreDimensions(stdout)) {
|
|
218
171
|
// Transient invalid dimensions (e.g. during maximize/restore on
|
|
@@ -234,12 +187,12 @@ export function startApp({
|
|
|
234
187
|
const cleanup = () => {
|
|
235
188
|
if (cleanupDone) return;
|
|
236
189
|
cleanupDone = true;
|
|
237
|
-
|
|
238
|
-
stdout.off("resize", onResize);
|
|
190
|
+
stdout.off("resize", onResize);
|
|
239
191
|
renderHandle?.cleanup();
|
|
240
192
|
// Restore terminal state: disable mouse reporting and bracketed paste.
|
|
241
193
|
terminal.setMouseReporting(false, "src/index.tsx:cleanup.mouse");
|
|
242
194
|
terminal.setBracketedPaste(false, "src/index.tsx:cleanup.bracketedPaste");
|
|
195
|
+
terminal.setAlternateScreen(false, "src/index.tsx:cleanup.alternateScreen");
|
|
243
196
|
terminal.resetModes();
|
|
244
197
|
activeRoot = null;
|
|
245
198
|
};
|
|
@@ -271,11 +224,12 @@ export function startApp({
|
|
|
271
224
|
|
|
272
225
|
renderHandle = renderApp(<App launchArgs={launchArgs} />, {
|
|
273
226
|
kittyKeyboard: KITTY_KEYBOARD_OPTIONS,
|
|
227
|
+
stdout: wrappedStdout as any,
|
|
274
228
|
});
|
|
275
229
|
|
|
276
230
|
// Resolve the real Ink class instance to get access to lastOutput,
|
|
277
231
|
// onRender, calculateLayout, etc. Gracefully degrades to null in tests.
|
|
278
|
-
inkInstance = resolveInkInstanceForStdout(
|
|
232
|
+
inkInstance = resolveInkInstanceForStdout(wrappedStdout);
|
|
279
233
|
|
|
280
234
|
// Remove Ink's own resize handler so the app is the sole resize handler.
|
|
281
235
|
// 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
|
|