@golba98/codexa 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +396 -100
- package/bin/codexa.js +62 -144
- package/package.json +14 -8
- package/src/app.tsx +596 -306
- package/src/commands/handler.ts +6 -6
- package/src/config/buildInfo.ts +2 -2
- package/src/config/layeredConfig.ts +1 -1
- package/src/config/persistence.ts +10 -0
- package/src/config/runtimeConfig.ts +1 -1
- package/src/config/settings.ts +8 -16
- package/src/config/trustStore.ts +1 -1
- package/src/config/updateCheckCache.ts +19 -1
- package/src/core/README.md +52 -0
- package/src/core/agent/loop.ts +282 -0
- package/src/core/agent/protocol.ts +211 -0
- package/src/core/agent/tools.ts +414 -0
- package/src/core/{codexExecArgs.ts → codex/codexExecArgs.ts} +2 -2
- package/src/core/{codexLaunch.ts → codex/codexLaunch.ts} +3 -3
- package/src/core/{codexPrompt.ts → codex/codexPrompt.ts} +3 -3
- package/src/core/debug/modelStateDebug.ts +34 -0
- package/src/core/executables/antigravityExecutable.ts +48 -0
- package/src/core/executables/codexExecutable.ts +1 -0
- package/src/core/executables/executableResolver.ts +65 -43
- package/src/core/perf/renderDebug.ts +10 -6
- package/src/core/process/processValidation.ts +9 -5
- package/src/core/providerLauncher/launcher.ts +59 -42
- package/src/core/providerLauncher/registry.ts +30 -14
- package/src/core/providerLauncher/types.ts +11 -9
- package/src/core/providerLauncher/workspaceConfig.ts +41 -26
- package/src/core/providerRuntime/anthropic.ts +7 -1
- package/src/core/providerRuntime/antigravity.ts +305 -0
- package/src/core/providerRuntime/claudeCodeDiscovery.ts +268 -22
- package/src/core/providerRuntime/claudeCodeDiscoveryDebug.ts +55 -0
- package/src/core/providerRuntime/contextMetadata.ts +12 -24
- package/src/core/providerRuntime/local.ts +129 -51
- package/src/core/providerRuntime/models.ts +22 -11
- package/src/core/providerRuntime/registry.ts +58 -31
- package/src/core/providerRuntime/types.ts +19 -14
- package/src/core/providers/codexSubprocess.ts +2 -2
- package/src/core/providers/types.ts +1 -1
- package/src/core/{attachments.ts → shared/attachments.ts} +27 -4
- package/src/core/{cleanupFastFail.ts → shared/cleanupFastFail.ts} +1 -1
- package/src/core/{hollowResponseFormat.ts → shared/hollowResponseFormat.ts} +1 -1
- package/src/core/terminal/clearFrameBoundary.ts +814 -0
- package/src/core/terminal/frameLock.ts +109 -0
- package/src/core/terminal/inkRenderReset.ts +123 -0
- package/src/core/terminal/terminalControl.ts +22 -0
- package/src/core/terminal/terminalTitle.ts +16 -102
- package/src/core/version/channel.ts +23 -0
- package/src/core/version/updateCheck.ts +193 -0
- package/src/core/{launchContext.ts → workspace/launchContext.ts} +32 -39
- package/src/core/{planStorage.ts → workspace/planStorage.ts} +2 -2
- package/src/core/{workspaceGuard.ts → workspace/workspaceGuard.ts} +97 -13
- package/src/headless/execRunner.ts +2 -2
- package/src/index.tsx +43 -89
- package/src/session/appSession.ts +10 -7
- package/src/session/chatLifecycle.ts +1 -1
- package/src/session/liveRenderScheduler.ts +1 -1
- package/src/session/types.ts +3 -2
- package/src/ui/ActionRequiredBlock.tsx +5 -5
- package/src/ui/ActivityBars.tsx +3 -3
- package/src/ui/ActivityIndicator.tsx +6 -6
- package/src/ui/AgentBlock.tsx +6 -6
- package/src/ui/AnimatedStatusText.tsx +1 -1
- package/src/ui/AppShell.tsx +670 -719
- package/src/ui/AttachmentImportPanel.tsx +8 -8
- package/src/ui/AuthPanel.tsx +20 -20
- package/src/ui/BottomComposer.tsx +158 -118
- package/src/ui/DashCard.tsx +3 -3
- package/src/ui/Markdown.tsx +17 -17
- package/src/ui/ModelPickerScreen.tsx +222 -42
- package/src/ui/ModelReasoningPicker.tsx +15 -15
- package/src/ui/Panel.tsx +3 -3
- package/src/ui/PlanActionPicker.tsx +6 -6
- package/src/ui/PlanReviewPanel.tsx +9 -9
- package/src/ui/ProviderPicker.tsx +735 -321
- package/src/ui/RunFooter.tsx +3 -3
- package/src/ui/RuntimeStatusBar.tsx +108 -0
- package/src/ui/SelectionPanel.tsx +8 -4
- package/src/ui/SettingsPanel.tsx +9 -9
- package/src/ui/Spinner.tsx +1 -1
- package/src/ui/TextEntryPanel.tsx +11 -11
- package/src/ui/ThinkingBlock.tsx +8 -8
- package/src/ui/Timeline.tsx +1625 -1472
- package/src/ui/TopHeader.tsx +437 -293
- package/src/ui/TranscriptShell.tsx +322 -0
- package/src/ui/TurnGroup.tsx +33 -33
- package/src/ui/UpdateAvailableCard.tsx +41 -0
- package/src/ui/UpdatePromptPanel.tsx +197 -0
- package/src/ui/focus.ts +3 -0
- package/src/ui/layout.ts +299 -25
- package/src/ui/layoutListWindow.ts +145 -0
- package/src/ui/logoVariants.ts +103 -0
- package/src/ui/modeDisplay.ts +12 -12
- package/src/ui/runtimeDisplay.ts +112 -0
- package/src/ui/textLayout.ts +15 -4
- package/src/ui/theme.tsx +274 -395
- package/src/ui/timelineMeasure.ts +218 -136
- package/scripts/audit-codexa-capabilities.mjs +0 -466
- package/scripts/gen-build-info.mjs +0 -33
- package/scripts/smoke-terminal-bench.mjs +0 -35
- package/src/appRenderStability.test.ts +0 -131
- package/src/commands/handler.test.ts +0 -655
- package/src/config/launchArgs.test.ts +0 -189
- package/src/config/layeredConfig.test.ts +0 -143
- package/src/config/persistence.test.ts +0 -114
- package/src/config/runtimeConfig.test.ts +0 -218
- package/src/config/settings.test.ts +0 -155
- package/src/config/trustStore.test.ts +0 -29
- package/src/core/attachments.test.ts +0 -155
- package/src/core/auth/codexAuth.test.ts +0 -68
- package/src/core/cleanupFastFail.test.ts +0 -76
- package/src/core/codex.ts +0 -124
- package/src/core/codexExecArgs.test.ts +0 -195
- package/src/core/codexLaunch.test.ts +0 -205
- package/src/core/codexPrompt.test.ts +0 -252
- package/src/core/executables/codexExecutable.test.ts +0 -212
- package/src/core/executables/executableResolver.test.ts +0 -129
- package/src/core/executables/geminiExecutable.test.ts +0 -116
- package/src/core/executables/pathSanityScan.test.ts +0 -47
- package/src/core/githubDiagnostics.test.ts +0 -92
- package/src/core/hollowResponseFormat.test.ts +0 -58
- package/src/core/launchContext.test.ts +0 -157
- package/src/core/models/codexCapabilities.test.ts +0 -45
- package/src/core/models/codexModelCapabilities.test.ts +0 -246
- package/src/core/models/modelSpecs.test.ts +0 -283
- package/src/core/perf/renderDebug.test.ts +0 -230
- package/src/core/planStorage.test.ts +0 -143
- package/src/core/process/CommandRunner.test.ts +0 -105
- package/src/core/projectInstructions.test.ts +0 -50
- package/src/core/providerLauncher/launcher.test.ts +0 -238
- package/src/core/providerLauncher/registry.test.ts +0 -324
- package/src/core/providerLauncher/workspaceConfig.test.ts +0 -638
- package/src/core/providerRuntime/anthropic.test.ts +0 -1120
- package/src/core/providerRuntime/capabilityProfile.test.ts +0 -311
- package/src/core/providerRuntime/contextMetadata.test.ts +0 -468
- package/src/core/providerRuntime/gemini.test.ts +0 -437
- package/src/core/providerRuntime/lmstudio.test.ts +0 -168
- package/src/core/providerRuntime/local.test.ts +0 -787
- package/src/core/providerRuntime/registry.test.ts +0 -233
- package/src/core/providers/codexJsonStream.test.ts +0 -148
- package/src/core/providers/codexSubprocess.test.ts +0 -68
- package/src/core/providers/codexTranscript.test.ts +0 -284
- package/src/core/terminal/startupClear.test.ts +0 -55
- package/src/core/terminal/terminalCapabilities.test.ts +0 -93
- package/src/core/terminal/terminalControl.test.ts +0 -75
- package/src/core/terminal/terminalSanitize.test.ts +0 -22
- package/src/core/terminal/terminalSelection.test.ts +0 -42
- package/src/core/terminal/terminalTitle.test.ts +0 -328
- package/src/core/updateCheck.test.ts +0 -194
- package/src/core/updateCheck.ts +0 -172
- package/src/core/workspaceActivity.test.ts +0 -163
- package/src/core/workspaceGuard.test.ts +0 -151
- package/src/core/workspaceRoot.test.ts +0 -23
- package/src/exec.test.ts +0 -13
- package/src/headless/execArgs.test.ts +0 -147
- package/src/headless/execRunner.test.ts +0 -436
- package/src/index.test.tsx +0 -620
- package/src/session/appSession.test.ts +0 -897
- package/src/session/chatLifecycle.test.ts +0 -64
- package/src/session/liveRenderScheduler.test.ts +0 -201
- package/src/session/planFlow.test.ts +0 -103
- package/src/session/planTranscript.test.ts +0 -65
- package/src/session/promptRunSchedule.test.ts +0 -36
- package/src/ui/ActivityIndicator.test.tsx +0 -58
- package/src/ui/AgentBlock.test.ts +0 -6
- package/src/ui/AnimatedStatusText.test.ts +0 -16
- package/src/ui/AppShell.test.tsx +0 -1776
- package/src/ui/AttachmentImportPanel.test.tsx +0 -204
- package/src/ui/BottomComposer.test.ts +0 -674
- package/src/ui/CodexLogo.tsx +0 -55
- package/src/ui/Markdown.test.ts +0 -157
- package/src/ui/ModelPickerProviderScope.test.tsx +0 -411
- package/src/ui/ModelPickerScreen.test.tsx +0 -99
- package/src/ui/ModelPickerState.test.tsx +0 -151
- package/src/ui/ModelReasoningPicker.test.tsx +0 -447
- package/src/ui/PlanReviewPanel.test.tsx +0 -267
- package/src/ui/PromptCardBorder.test.tsx +0 -161
- package/src/ui/ProviderPicker.test.tsx +0 -289
- package/src/ui/ProviderShortcut.test.tsx +0 -143
- package/src/ui/SettingsPanel.test.tsx +0 -233
- package/src/ui/StaticTranscriptItem.tsx +0 -56
- package/src/ui/Timeline.test.ts +0 -2067
- package/src/ui/TimelineNavigation.test.tsx +0 -201
- package/src/ui/TopHeader.test.tsx +0 -254
- package/src/ui/TurnGroup.test.tsx +0 -365
- package/src/ui/busyStatusAnimation.test.ts +0 -30
- package/src/ui/commandNormalize.test.ts +0 -142
- package/src/ui/diffRenderer.test.ts +0 -102
- package/src/ui/focusFlow.test.tsx +0 -1098
- package/src/ui/inputBuffer.test.ts +0 -151
- package/src/ui/layout.test.ts +0 -146
- package/src/ui/modeDisplay.test.ts +0 -42
- package/src/ui/runActivityView.test.ts +0 -89
- package/src/ui/runLifecycleView.test.tsx +0 -237
- package/src/ui/statusRenderIsolation.test.tsx +0 -654
- package/src/ui/terminalAnswerFormat.test.ts +0 -19
- package/src/ui/textLayout.test.ts +0 -18
- package/src/ui/themeFlow.test.ts +0 -53
- package/src/ui/timelineMeasureCache.test.ts +0 -986
- /package/src/core/{inputDebug.ts → debug/inputDebug.ts} +0 -0
- /package/src/core/{clipboard.ts → shared/clipboard.ts} +0 -0
- /package/src/core/{githubDiagnostics.ts → shared/githubDiagnostics.ts} +0 -0
- /package/src/core/{projectInstructions.ts → workspace/projectInstructions.ts} +0 -0
- /package/src/core/{workspaceActivity.ts → workspace/workspaceActivity.ts} +0 -0
- /package/src/core/{workspaceRoot.ts → workspace/workspaceRoot.ts} +0 -0
package/src/commands/handler.ts
CHANGED
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
type WorkspaceDisplayMode,
|
|
37
37
|
} from "../config/settings.js";
|
|
38
38
|
|
|
39
|
-
import type { WorkspaceCommandContext } from "../core/launchContext.js";
|
|
39
|
+
import type { WorkspaceCommandContext } from "../core/workspace/launchContext.js";
|
|
40
40
|
import {
|
|
41
41
|
findModelCapability,
|
|
42
42
|
formatModelCapabilitiesList,
|
|
@@ -120,7 +120,7 @@ export interface CommandContext {
|
|
|
120
120
|
modelCapabilities?: CodexModelCapabilities | null;
|
|
121
121
|
routeStatusMessage?: string;
|
|
122
122
|
activeRouteProviderLabel?: string;
|
|
123
|
-
projectInstructions?: import("../core/projectInstructions.js").ProjectInstructionsLoadResult | null;
|
|
123
|
+
projectInstructions?: import("../core/workspace/projectInstructions.js").ProjectInstructionsLoadResult | null;
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
// Mirrors AVAILABLE_APPROVAL_POLICIES[].id from runtimeConfig.ts
|
|
@@ -398,10 +398,10 @@ function buildHelpMessage(context: CommandContext): string {
|
|
|
398
398
|
" /copy Copy last response to clipboard",
|
|
399
399
|
" /update [check] Show update status and instructions for updating Codexa",
|
|
400
400
|
" /help Show this help",
|
|
401
|
-
"",
|
|
402
|
-
"
|
|
403
|
-
" npm
|
|
404
|
-
"
|
|
401
|
+
"",
|
|
402
|
+
"Local development:",
|
|
403
|
+
" npm run install:dev-bin Install the codexa-dev command",
|
|
404
|
+
" codexa-dev Run this repo without replacing codexa",
|
|
405
405
|
"",
|
|
406
406
|
"Shortcuts:",
|
|
407
407
|
" Ctrl+B Open backend picker",
|
package/src/config/buildInfo.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// Generated by scripts/gen-build-info.mjs — do not edit manually.
|
|
2
|
-
export const BUILD_COMMIT = "
|
|
3
|
-
export const
|
|
2
|
+
export const BUILD_COMMIT = "b066a15b77696d2a0945ea7c6c3d07630584296b" as const;
|
|
3
|
+
export const APP_VERSION = "1.0.4" as const;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "fs";
|
|
2
2
|
import { dirname, join, resolve } from "path";
|
|
3
|
-
import { normalizeWorkspaceRoot } from "../core/workspaceRoot.js";
|
|
3
|
+
import { normalizeWorkspaceRoot } from "../core/workspace/workspaceRoot.js";
|
|
4
4
|
import {
|
|
5
5
|
formatApprovalPolicyLabel,
|
|
6
6
|
formatNetworkAccessLabel,
|
|
@@ -50,6 +50,7 @@ export interface AuthSettings {
|
|
|
50
50
|
export interface UpdateCheckSettings {
|
|
51
51
|
enabled: boolean;
|
|
52
52
|
intervalHours: number;
|
|
53
|
+
skippedUpdateVersion?: string | null;
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
export const DEFAULT_UPDATE_CHECK_SETTINGS: UpdateCheckSettings = {
|
|
@@ -125,6 +126,7 @@ function normalizeUpdateCheckSettings(
|
|
|
125
126
|
intervalHours: typeof input?.intervalHours === "number" && input.intervalHours > 0
|
|
126
127
|
? input.intervalHours
|
|
127
128
|
: DEFAULT_UPDATE_CHECK_SETTINGS.intervalHours,
|
|
129
|
+
skippedUpdateVersion: input?.skippedUpdateVersion ?? null,
|
|
128
130
|
};
|
|
129
131
|
}
|
|
130
132
|
|
|
@@ -269,6 +271,11 @@ export function parseSettingsData(data: unknown): AppSettings {
|
|
|
269
271
|
: typeof updateCheckSource.interval_hours === "number"
|
|
270
272
|
? updateCheckSource.interval_hours
|
|
271
273
|
: undefined,
|
|
274
|
+
skippedUpdateVersion: typeof updateCheckSource.skippedUpdateVersion === "string"
|
|
275
|
+
? updateCheckSource.skippedUpdateVersion
|
|
276
|
+
: typeof updateCheckSource.skipped_update_version === "string"
|
|
277
|
+
? updateCheckSource.skipped_update_version
|
|
278
|
+
: null,
|
|
272
279
|
}),
|
|
273
280
|
};
|
|
274
281
|
}
|
|
@@ -299,6 +306,9 @@ export function serializeSettings(settings: AppSettings): Record<string, unknown
|
|
|
299
306
|
update_check: {
|
|
300
307
|
enabled: settings.updateCheck.enabled,
|
|
301
308
|
interval_hours: settings.updateCheck.intervalHours,
|
|
309
|
+
...(settings.updateCheck.skippedUpdateVersion != null
|
|
310
|
+
? { skipped_update_version: settings.updateCheck.skippedUpdateVersion }
|
|
311
|
+
: {}),
|
|
302
312
|
},
|
|
303
313
|
};
|
|
304
314
|
}
|
|
@@ -106,7 +106,7 @@ export interface ResolvedRuntimeConfig {
|
|
|
106
106
|
export interface RuntimeStatusContext {
|
|
107
107
|
workspaceRoot: string;
|
|
108
108
|
tokensUsed?: number | null;
|
|
109
|
-
projectInstructions?: import("../core/projectInstructions.js").ProjectInstructionsLoadResult | null;
|
|
109
|
+
projectInstructions?: import("../core/workspace/projectInstructions.js").ProjectInstructionsLoadResult | null;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
export interface RuntimeSummary {
|
package/src/config/settings.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { homedir } from "os";
|
|
2
|
-
import { basename, join, parse, win32 } from "path";
|
|
3
|
-
import { BUILD_VERSION } from "./buildInfo.js";
|
|
1
|
+
import { homedir } from "os";
|
|
2
|
+
import { basename, join, parse, win32 } from "path";
|
|
4
3
|
|
|
5
4
|
function isWindowsStylePath(p: string): boolean {
|
|
6
5
|
return /^[A-Za-z]:[\\/]/.test(p) || /^\\\\/.test(p);
|
|
@@ -10,14 +9,14 @@ function smartJoin(base: string, ...parts: string[]): string {
|
|
|
10
9
|
return isWindowsStylePath(base) ? win32.join(base, ...parts) : join(base, ...parts);
|
|
11
10
|
}
|
|
12
11
|
|
|
13
|
-
export
|
|
14
|
-
export const
|
|
12
|
+
export { APP_VERSION } from "./buildInfo.js";
|
|
13
|
+
export const APP_NAME = "Codexa";
|
|
15
14
|
export const DEFAULT_BACKEND = "codex-subprocess";
|
|
16
15
|
export const DEFAULT_MODEL = "gpt-5.4";
|
|
17
16
|
export const DEFAULT_MODE = "full-auto";
|
|
18
17
|
export const DEFAULT_REASONING_LEVEL = "high";
|
|
19
18
|
export const DEFAULT_LAYOUT_STYLE = "gemini-shell";
|
|
20
|
-
export const DEFAULT_THEME = "
|
|
19
|
+
export const DEFAULT_THEME = "dark";
|
|
21
20
|
export const DEFAULT_WORKSPACE_DISPLAY_MODE = "dir";
|
|
22
21
|
export const DEFAULT_TERMINAL_TITLE_MODE = "dir";
|
|
23
22
|
export const DEFAULT_SHOW_BUSY_LOADER = true;
|
|
@@ -102,7 +101,7 @@ export const TERMINAL_MOUSE_MODES = ["wheel", "selection"] as const;
|
|
|
102
101
|
|
|
103
102
|
export type TerminalMouseMode = (typeof TERMINAL_MOUSE_MODES)[number];
|
|
104
103
|
|
|
105
|
-
export const DEFAULT_TERMINAL_MOUSE_MODE: TerminalMouseMode = "
|
|
104
|
+
export const DEFAULT_TERMINAL_MOUSE_MODE: TerminalMouseMode = "wheel";
|
|
106
105
|
|
|
107
106
|
export interface SettingOption<TValue extends string> {
|
|
108
107
|
value: TValue;
|
|
@@ -265,21 +264,14 @@ export function formatReasoningLabel(reasoning: string): string {
|
|
|
265
264
|
}
|
|
266
265
|
|
|
267
266
|
export const AVAILABLE_THEMES = [
|
|
267
|
+
{ id: "dark", label: "Codexa Dark" },
|
|
268
268
|
{ id: "purple", label: "Midnight Purple" },
|
|
269
269
|
{ id: "mono", label: "Black & White" },
|
|
270
|
-
{ id: "dark", label: "Modern Dark" },
|
|
271
270
|
{ id: "black", label: "Codex the Black" },
|
|
272
|
-
{ id: "emerald", label: "Emerald Night" },
|
|
273
|
-
{ id: "solar", label: "Solar Flare" },
|
|
274
|
-
{ id: "cyber", label: "Cyberpunk Neon" },
|
|
275
|
-
{ id: "ocean", label: "Deep Oceanic" },
|
|
276
271
|
{ id: "nordic", label: "Nordic Frost" },
|
|
277
|
-
{ id: "green", label: "Terminal Green" },
|
|
278
|
-
{ id: "amber", label: "Terminal Amber" },
|
|
279
|
-
{ id: "vaporwave", label: "Vaporwave Dream" },
|
|
280
272
|
{ id: "dracula", label: "Dracula Night" },
|
|
281
273
|
{ id: "gruvbox", label: "Gruvbox Hard" },
|
|
282
|
-
{ id: "
|
|
274
|
+
{ id: "ocean", label: "Deep Oceanic" },
|
|
283
275
|
{ id: "custom", label: "Customize..." },
|
|
284
276
|
] as const;
|
|
285
277
|
|
package/src/config/trustStore.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
|
|
2
2
|
import { dirname } from "path";
|
|
3
3
|
import { getCodexaTrustStoreFile } from "./settings.js";
|
|
4
|
-
import { normalizeWorkspaceRoot } from "../core/workspaceRoot.js";
|
|
4
|
+
import { normalizeWorkspaceRoot } from "../core/workspace/workspaceRoot.js";
|
|
5
5
|
|
|
6
6
|
interface TrustStoreData {
|
|
7
7
|
trustedProjectRoots: string[];
|
|
@@ -39,7 +39,25 @@ export function saveUpdateCheckCache(cache: UpdateCheckCache): void {
|
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
function stripV(v: string): string {
|
|
43
|
+
return v.startsWith("v") ? v.slice(1) : v;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Returns true only when the cache is still usable:
|
|
48
|
+
* - `runningVersion` matches the version the cache was written for (version-mismatch = stale)
|
|
49
|
+
* - The cache was written within `intervalHours`
|
|
50
|
+
*/
|
|
51
|
+
export function isCacheValid(
|
|
52
|
+
cache: UpdateCheckCache,
|
|
53
|
+
intervalHours: number,
|
|
54
|
+
runningVersion?: string,
|
|
55
|
+
): boolean {
|
|
56
|
+
if (runningVersion !== undefined) {
|
|
57
|
+
if (stripV(cache.currentVersion) !== stripV(runningVersion)) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
43
61
|
const maxAgeMs = intervalHours * 60 * 60 * 1000;
|
|
44
62
|
return Date.now() - cache.lastChecked < maxAgeMs;
|
|
45
63
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# `src/core`
|
|
2
|
+
|
|
3
|
+
Non-UI runtime logic for Codexa: launching backends, talking to provider CLIs,
|
|
4
|
+
terminal I/O, workspace resolution, and supporting utilities. UI lives in `src/ui`,
|
|
5
|
+
app state in `src/session`, config in `src/config`. The top level of `src/core` is
|
|
6
|
+
folders-only — every file lives in a domain folder so the directory stays scannable.
|
|
7
|
+
|
|
8
|
+
## Folder map
|
|
9
|
+
|
|
10
|
+
| Folder | Responsibility |
|
|
11
|
+
| --- | --- |
|
|
12
|
+
| `providers/` | **Low-level Codex subprocess I/O.** Spawns the `codex` binary and parses its output (`codexSubprocess`, `codexJsonStream`, `codexTranscript`), plus the backend registry/types and the `openaiNative` stub. |
|
|
13
|
+
| `providerRuntime/` | **Multi-provider runtimes + discovery.** One runtime per provider (`anthropic`, `gemini`, `local`, `antigravity`) behind a shared interface, plus routing (`registry`), model/metadata helpers (`models`, `capabilityProfile`, `contextMetadata`, `reasoning`) and Claude Code discovery. |
|
|
14
|
+
| `providerLauncher/` | **Workspace provider config + CLI launching.** Which provider is active per workspace (`workspaceConfig`), provider UI state (`registry`), and spawning provider CLIs (`launcher`). |
|
|
15
|
+
| `codex/` | Codex CLI launch/prompt assembly: `codexExecArgs`, `codexLaunch`, `codexPrompt`. |
|
|
16
|
+
| `models/` | Codex CLI capability discovery (`codexCapabilities`, `codexModelCapabilities`) and the legacy model-spec service (`modelSpecs`). |
|
|
17
|
+
| `executables/` | Resolve external CLI binaries with PATH/env handling (`executableResolver` + per-CLI resolvers). |
|
|
18
|
+
| `auth/` | Codex auth status probing. |
|
|
19
|
+
| `process/` | Generic process spawning (`CommandRunner`) and executable-path validation. |
|
|
20
|
+
| `terminal/` | Terminal I/O: ANSI sanitize, raw mode / cursor, title sequences, capability detection, and the `/clear` + resize repaint boundary (`clearFrameBoundary`, `inkRenderReset`). |
|
|
21
|
+
| `workspace/` | Workspace resolution and state: `workspaceRoot`, `workspaceGuard`, `workspaceActivity`, `projectInstructions`, `planStorage`, `launchContext`. |
|
|
22
|
+
| `version/` | Build channel / version branding (`channel`) and update checking (`updateCheck`). |
|
|
23
|
+
| `shared/` | Small cross-cutting utilities: `clipboard`, `cleanupFastFail`, `githubDiagnostics`, `attachments`, `hollowResponseFormat`. |
|
|
24
|
+
| `perf/` | Performance + render instrumentation (`profiler`, `renderDebug`). |
|
|
25
|
+
| `debug/` | Dev-only tracing helpers (`inputDebug`). |
|
|
26
|
+
|
|
27
|
+
## The three provider layers
|
|
28
|
+
|
|
29
|
+
`providers/`, `providerRuntime/`, and `providerLauncher/` have similar names but are
|
|
30
|
+
distinct layers — they are **not** duplicates:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
providerLauncher/ which provider is active for this workspace + how to spawn its CLI
|
|
34
|
+
│
|
|
35
|
+
providerRuntime/ per-provider runtimes (anthropic/gemini/local/antigravity),
|
|
36
|
+
│ routing, model discovery, capability/context metadata
|
|
37
|
+
│
|
|
38
|
+
providers/ low-level Codex subprocess I/O + output parsing
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The default Codex backend flows through `providers/`; the other providers are
|
|
42
|
+
implemented as `providerRuntime/` runtimes.
|
|
43
|
+
|
|
44
|
+
## Debug instrumentation
|
|
45
|
+
|
|
46
|
+
These are intentional, env-gated diagnostics (not dead code) — keep them named clearly:
|
|
47
|
+
|
|
48
|
+
- `debug/inputDebug.ts` — stdin state tracing (`CODEXA_DEBUG_INPUT=1`).
|
|
49
|
+
- `perf/renderDebug.ts` — Ink render/flicker tracing (`CODEXA_RENDER_DEBUG=1`). Kept in
|
|
50
|
+
`perf/` rather than `debug/` because it is imported widely across the UI.
|
|
51
|
+
- `providerRuntime/claudeCodeDiscoveryDebug.ts` — entry point for the
|
|
52
|
+
`bun run debug:claude-models` script; lives next to the discovery code it exercises.
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import type { BackendRunHandlers } from "../providers/types.js";
|
|
4
|
+
import type { ProviderChatRequest } from "../providerRuntime/types.js";
|
|
5
|
+
import { executeAgentTool, type AgentToolResult } from "./tools.js";
|
|
6
|
+
import { parseAgentToolCall, serializeToolResult, type NormalizedAgentToolCall } from "./protocol.js";
|
|
7
|
+
|
|
8
|
+
export interface AgentChatMessage {
|
|
9
|
+
role: "system" | "user" | "assistant" | "tool";
|
|
10
|
+
content: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface AgentChatResponse {
|
|
14
|
+
text: string;
|
|
15
|
+
toolCalls?: readonly NormalizedAgentToolCall[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface RunAgentLoopOptions {
|
|
19
|
+
request: ProviderChatRequest;
|
|
20
|
+
handlers: BackendRunHandlers;
|
|
21
|
+
sendMessages: (messages: readonly AgentChatMessage[], turnIndex: number) => Promise<AgentChatResponse>;
|
|
22
|
+
includeSystemPrompt: boolean;
|
|
23
|
+
signal?: AbortSignal;
|
|
24
|
+
maxToolCalls?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const DEFAULT_MAX_TOOL_CALLS = 10;
|
|
28
|
+
|
|
29
|
+
function workspaceSummary(workspaceRoot: string): string {
|
|
30
|
+
const lines = [`Workspace root: ${workspaceRoot}`];
|
|
31
|
+
try {
|
|
32
|
+
const entries = readdirSync(workspaceRoot, { withFileTypes: true })
|
|
33
|
+
.filter((entry) => ![".git", "node_modules", "dist", "build", "coverage"].includes(entry.name))
|
|
34
|
+
.map((entry) => `${entry.name}${entry.isDirectory() ? "/" : ""}`)
|
|
35
|
+
.sort()
|
|
36
|
+
.slice(0, 20);
|
|
37
|
+
if (entries.length > 0) lines.push(`Top-level entries: ${entries.join(", ")}`);
|
|
38
|
+
} catch {
|
|
39
|
+
// Tool-based inspection remains available when a shallow summary is unavailable.
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const packageJsonPath = path.join(workspaceRoot, "package.json");
|
|
44
|
+
if (existsSync(packageJsonPath)) {
|
|
45
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8")) as Record<string, unknown>;
|
|
46
|
+
const name = typeof packageJson.name === "string" ? packageJson.name : null;
|
|
47
|
+
const description = typeof packageJson.description === "string" ? packageJson.description : null;
|
|
48
|
+
if (name) lines.push(`Package: ${name}${description ? ` - ${description}` : ""}`);
|
|
49
|
+
}
|
|
50
|
+
} catch {
|
|
51
|
+
// A malformed package.json should not prevent a local chat request.
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return lines.join("\n");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function localAgentSystemPrompt(request: ProviderChatRequest): string {
|
|
58
|
+
const hasCargoToml = existsSync(path.join(request.workspaceRoot, "Cargo.toml"));
|
|
59
|
+
return [
|
|
60
|
+
`You are an autonomous coding assistant running inside this workspace: ${request.workspaceRoot}`,
|
|
61
|
+
"You must inspect files with tools before claiming you cannot see them.",
|
|
62
|
+
"For broad questions about the repository, use the workspace summary below, then inspect with get_workspace_info or list_files before answering when more detail is needed.",
|
|
63
|
+
"Use tools to create, edit, build, and test when the user asks for workspace changes.",
|
|
64
|
+
"Do not ask vague clarification questions when the user's intent has an obvious safe implementation.",
|
|
65
|
+
hasCargoToml
|
|
66
|
+
? "Rust workspace note: Cargo.toml exists. Prefer src/main.rs for simple binaries, use cargo check for validation, use cargo run for running, and do not use rustc main.rs unless main.rs is truly at the workspace root."
|
|
67
|
+
: null,
|
|
68
|
+
"Use exactly one tool call at a time in this format:",
|
|
69
|
+
'<tool_call>{"name":"read_file","arguments":{"path":"src/index.tsx"}}</tool_call>',
|
|
70
|
+
"Available tools: list_files, read_file, write_file, apply_patch, run_shell, get_workspace_info.",
|
|
71
|
+
"Summarize changed files and commands run in your final answer.",
|
|
72
|
+
`Workspace summary:\n${workspaceSummary(request.workspaceRoot)}`,
|
|
73
|
+
request.projectInstructions?.content
|
|
74
|
+
? ["Project instructions:", request.projectInstructions.content].join("\n")
|
|
75
|
+
: null,
|
|
76
|
+
].filter(Boolean).join("\n\n");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function buildInitialMessages(request: ProviderChatRequest, includeSystemPrompt: boolean): AgentChatMessage[] {
|
|
80
|
+
const systemPrompt = localAgentSystemPrompt(request);
|
|
81
|
+
if (includeSystemPrompt) {
|
|
82
|
+
return [
|
|
83
|
+
{ role: "system", content: systemPrompt },
|
|
84
|
+
{ role: "user", content: request.prompt },
|
|
85
|
+
];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return [
|
|
89
|
+
{ role: "user", content: `${systemPrompt}\n\nUser request:\n${request.prompt}` },
|
|
90
|
+
];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function toolActivityCommand(result: Pick<AgentToolResult, "tool" | "path" | "paths" | "command">): string {
|
|
94
|
+
if (result.command) return `${result.tool}: ${result.command}`;
|
|
95
|
+
if (result.path) return `${result.tool}: ${result.path}`;
|
|
96
|
+
if (result.paths && result.paths.length > 0) return `${result.tool}: ${result.paths.join(", ")}`;
|
|
97
|
+
return result.tool;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
interface ExecutedCommand {
|
|
101
|
+
command: string;
|
|
102
|
+
success: boolean;
|
|
103
|
+
exitCode?: number | null;
|
|
104
|
+
durationMs?: number;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
interface AgentLoopSummary {
|
|
108
|
+
changedFiles: Set<string>;
|
|
109
|
+
commands: ExecutedCommand[];
|
|
110
|
+
toolResults: AgentToolResult[];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function stableJson(value: unknown): string {
|
|
114
|
+
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
115
|
+
if (value && typeof value === "object") {
|
|
116
|
+
const record = value as Record<string, unknown>;
|
|
117
|
+
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableJson(record[key])}`).join(",")}}`;
|
|
118
|
+
}
|
|
119
|
+
return JSON.stringify(value);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function toolCallSignature(call: Pick<NormalizedAgentToolCall, "name" | "arguments">): string {
|
|
123
|
+
return `${call.name}:${stableJson(call.arguments)}`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function recordToolResult(summary: AgentLoopSummary, result: AgentToolResult): void {
|
|
127
|
+
for (const file of result.paths ?? []) {
|
|
128
|
+
if (file) summary.changedFiles.add(file);
|
|
129
|
+
}
|
|
130
|
+
if (result.path && (result.tool === "write_file" || result.tool === "apply_patch")) {
|
|
131
|
+
summary.changedFiles.add(result.path);
|
|
132
|
+
}
|
|
133
|
+
if (result.command) {
|
|
134
|
+
summary.commands.push({
|
|
135
|
+
command: result.command,
|
|
136
|
+
success: result.success,
|
|
137
|
+
exitCode: result.exitCode,
|
|
138
|
+
durationMs: result.durationMs,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
summary.toolResults.push(result);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function commandStatus(command: ExecutedCommand): string {
|
|
145
|
+
const status = command.success ? "succeeded" : "failed";
|
|
146
|
+
const exitCode = command.exitCode === undefined ? "" : `, exit ${command.exitCode ?? "n/a"}`;
|
|
147
|
+
return `- ${command.command}: ${status}${exitCode}`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function nextCommand(request: ProviderChatRequest, summary: AgentLoopSummary): string {
|
|
151
|
+
if (existsSync(path.join(request.workspaceRoot, "Cargo.toml"))) return "cargo run";
|
|
152
|
+
const lastValidation = [...summary.commands].reverse().find((item) =>
|
|
153
|
+
/\b(?:cargo check|cargo run|bun test|npm test|bun run typecheck|tsc)\b/.test(item.command)
|
|
154
|
+
);
|
|
155
|
+
if (lastValidation) return lastValidation.command;
|
|
156
|
+
return "bun test";
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function synthesizeFinalMessage(request: ProviderChatRequest, summary: AgentLoopSummary, reason: string): string {
|
|
160
|
+
const files = [...summary.changedFiles].sort();
|
|
161
|
+
const commandLines = summary.commands.map(commandStatus);
|
|
162
|
+
return [
|
|
163
|
+
reason,
|
|
164
|
+
"",
|
|
165
|
+
"Files changed:",
|
|
166
|
+
files.length > 0 ? files.map((file) => `- ${file}`).join("\n") : "- None detected",
|
|
167
|
+
"",
|
|
168
|
+
"Commands run:",
|
|
169
|
+
commandLines.length > 0 ? commandLines.join("\n") : "- None",
|
|
170
|
+
"",
|
|
171
|
+
`Next command: ${nextCommand(request, summary)}`,
|
|
172
|
+
].join("\n").trim();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function requestFinalAnswer(options: RunAgentLoopOptions, messages: AgentChatMessage[], toolCallCount: number, reason: string): Promise<string | null> {
|
|
176
|
+
messages.push({
|
|
177
|
+
role: "user",
|
|
178
|
+
content: [
|
|
179
|
+
reason,
|
|
180
|
+
"Stop calling tools now. Write the final answer using the tool results already provided.",
|
|
181
|
+
"Include files changed, commands run, whether they succeeded, and the next command the user can run.",
|
|
182
|
+
].join("\n"),
|
|
183
|
+
});
|
|
184
|
+
const response = await options.sendMessages(messages, toolCallCount);
|
|
185
|
+
const parsed = parseAgentToolCall(response.text);
|
|
186
|
+
return parsed.kind === "final" && parsed.text.trim() ? parsed.text.trim() : null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export async function runAgentLoop(options: RunAgentLoopOptions): Promise<string> {
|
|
190
|
+
const maxToolCalls = options.maxToolCalls ?? DEFAULT_MAX_TOOL_CALLS;
|
|
191
|
+
const messages = buildInitialMessages(options.request, options.includeSystemPrompt);
|
|
192
|
+
let toolCallCount = 0;
|
|
193
|
+
let previousToolSignature: string | null = null;
|
|
194
|
+
const summary: AgentLoopSummary = {
|
|
195
|
+
changedFiles: new Set(),
|
|
196
|
+
commands: [],
|
|
197
|
+
toolResults: [],
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
while (true) {
|
|
201
|
+
if (options.signal?.aborted) {
|
|
202
|
+
throw new Error("Local agent run was canceled.");
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const response = await options.sendMessages(messages, toolCallCount);
|
|
206
|
+
const structuredToolCall = response.toolCalls?.[0] ?? null;
|
|
207
|
+
const parsed = structuredToolCall
|
|
208
|
+
? { kind: "tool_call" as const, ...structuredToolCall, raw: JSON.stringify(structuredToolCall) }
|
|
209
|
+
: parseAgentToolCall(response.text);
|
|
210
|
+
|
|
211
|
+
if (parsed.kind === "final") {
|
|
212
|
+
return parsed.text.trim();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (toolCallCount >= maxToolCalls) {
|
|
216
|
+
const reason = `Local agent reached ${maxToolCalls} tool calls without a final answer.`;
|
|
217
|
+
const final = await requestFinalAnswer(options, messages, toolCallCount, reason);
|
|
218
|
+
return final ?? synthesizeFinalMessage(options.request, summary, reason);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (parsed.kind === "malformed_tool_call") {
|
|
222
|
+
messages.push({ role: "assistant", content: response.text });
|
|
223
|
+
toolCallCount += 1;
|
|
224
|
+
messages.push({
|
|
225
|
+
role: "user",
|
|
226
|
+
content: serializeToolResult({
|
|
227
|
+
success: false,
|
|
228
|
+
error: `Malformed tool call: ${parsed.error}`,
|
|
229
|
+
raw: parsed.raw,
|
|
230
|
+
}),
|
|
231
|
+
});
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const signature = toolCallSignature(parsed);
|
|
236
|
+
if (signature === previousToolSignature) {
|
|
237
|
+
const reason = `Local agent repeated the same ${parsed.name} tool call with identical arguments.`;
|
|
238
|
+
const final = await requestFinalAnswer(options, messages, toolCallCount, reason);
|
|
239
|
+
return final ?? synthesizeFinalMessage(options.request, summary, reason);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
messages.push({ role: "assistant", content: response.text || parsed.raw });
|
|
243
|
+
previousToolSignature = signature;
|
|
244
|
+
toolCallCount += 1;
|
|
245
|
+
const activityId = `local-agent-${toolCallCount}-${parsed.name}`;
|
|
246
|
+
const startedAt = Date.now();
|
|
247
|
+
const runningCommand = toolActivityCommand({
|
|
248
|
+
tool: parsed.name,
|
|
249
|
+
path: typeof parsed.arguments.path === "string" ? parsed.arguments.path : undefined,
|
|
250
|
+
command: typeof parsed.arguments.command === "string" ? parsed.arguments.command : undefined,
|
|
251
|
+
});
|
|
252
|
+
options.handlers.onToolActivity?.({
|
|
253
|
+
id: activityId,
|
|
254
|
+
command: runningCommand,
|
|
255
|
+
status: "running",
|
|
256
|
+
startedAt,
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
const result = await executeAgentTool(parsed.name, parsed.arguments, {
|
|
260
|
+
workspaceRoot: options.request.workspaceRoot,
|
|
261
|
+
runtime: options.request.runtime,
|
|
262
|
+
signal: options.signal,
|
|
263
|
+
});
|
|
264
|
+
const completedCommand = toolActivityCommand(result);
|
|
265
|
+
options.handlers.onToolActivity?.({
|
|
266
|
+
id: activityId,
|
|
267
|
+
command: completedCommand,
|
|
268
|
+
status: result.success ? "completed" : "failed",
|
|
269
|
+
startedAt,
|
|
270
|
+
completedAt: Date.now(),
|
|
271
|
+
summary: result.summary ?? result.error ?? null,
|
|
272
|
+
});
|
|
273
|
+
recordToolResult(summary, result);
|
|
274
|
+
|
|
275
|
+
messages.push({
|
|
276
|
+
role: "user",
|
|
277
|
+
content: serializeToolResult(result),
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
throw new Error(`Local agent stopped after ${maxToolCalls} tool calls without a final answer.`);
|
|
282
|
+
}
|