@golba98/codexa 1.0.4 → 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/package.json +1 -1
- package/src/app.tsx +128 -47
- package/src/commands/handler.ts +5 -16
- package/src/config/buildInfo.ts +2 -2
- 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/process/CommandRunner.ts +12 -1
- package/src/core/providerLauncher/registry.ts +37 -7
- package/src/core/providerLauncher/types.ts +2 -1
- package/src/core/providerRuntime/anthropic.ts +3 -5
- package/src/core/providerRuntime/antigravity.ts +246 -90
- package/src/core/providerRuntime/claudeCodeDiscovery.ts +724 -692
- package/src/core/providerRuntime/mistralVibe.ts +663 -0
- package/src/core/providerRuntime/models.ts +18 -4
- package/src/core/providerRuntime/reasoning.ts +9 -6
- package/src/core/providerRuntime/registry.ts +94 -49
- package/src/core/providerRuntime/types.ts +1 -0
- package/src/index.tsx +23 -9
- 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/{AppShell.tsx → chrome/AppShell.tsx} +11 -11
- package/src/ui/{BottomComposer.tsx → chrome/BottomComposer.tsx} +15 -15
- package/src/ui/{DashCard.tsx → chrome/DashCard.tsx} +2 -2
- package/src/ui/{RunFooter.tsx → chrome/RunFooter.tsx} +3 -3
- package/src/ui/{RuntimeStatusBar.tsx → chrome/RuntimeStatusBar.tsx} +3 -3
- package/src/ui/{Spinner.tsx → chrome/Spinner.tsx} +1 -1
- package/src/ui/{TopHeader.tsx → chrome/TopHeader.tsx} +12 -12
- package/src/ui/{UpdateAvailableCard.tsx → chrome/UpdateAvailableCard.tsx} +3 -3
- 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/{AttachmentImportPanel.tsx → panels/AttachmentImportPanel.tsx} +1 -1
- 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} +11 -9
- 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/{ProviderPicker.tsx → panels/ProviderPicker.tsx} +9 -7
- package/src/ui/{ReasoningPicker.tsx → panels/ReasoningPicker.tsx} +3 -3
- package/src/ui/{SelectionPanel.tsx → panels/SelectionPanel.tsx} +1 -1
- package/src/ui/{SettingsPanel.tsx → panels/SettingsPanel.tsx} +3 -3
- package/src/ui/{TextEntryPanel.tsx → panels/TextEntryPanel.tsx} +3 -3
- package/src/ui/{ThemePicker.tsx → panels/ThemePicker.tsx} +2 -2
- package/src/ui/{UpdatePromptPanel.tsx → panels/UpdatePromptPanel.tsx} +2 -2
- package/src/ui/{Markdown.tsx → render/Markdown.tsx} +2 -2
- package/src/ui/{diffRenderer.ts → render/diffRenderer.ts} +1 -1
- 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} +9 -8
- 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} +8 -8
- package/src/ui/{TranscriptShell.tsx → timeline/TranscriptShell.tsx} +6 -6
- package/src/ui/{TurnGroup.tsx → timeline/TurnGroup.tsx} +15 -15
- 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} +14 -14
- /package/src/ui/{busyStatusAnimation.ts → chrome/busyStatusAnimation.ts} +0 -0
- /package/src/ui/{slashCommands.ts → input/slashCommands.ts} +0 -0
- /package/src/ui/{logoVariants.ts → render/logoVariants.ts} +0 -0
- /package/src/ui/{terminalAnswerFormat.ts → render/terminalAnswerFormat.ts} +0 -0
- /package/src/ui/{textLayout.ts → render/textLayout.ts} +0 -0
- /package/src/ui/{layoutListWindow.ts → timeline/layoutListWindow.ts} +0 -0
package/package.json
CHANGED
package/src/app.tsx
CHANGED
|
@@ -15,12 +15,13 @@ function normalizeRuntimeAvailability(value: unknown): RuntimeAvailability {
|
|
|
15
15
|
return "unknown";
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
function formatRuntimeProviderLabel(providerId: ProviderId): string {
|
|
19
|
-
if (providerId === "local") return "Local";
|
|
20
|
-
if (providerId === "google") return "Google";
|
|
21
|
-
if (providerId === "anthropic") return "Anthropic";
|
|
22
|
-
if (providerId === "
|
|
23
|
-
return "
|
|
18
|
+
function formatRuntimeProviderLabel(providerId: ProviderId): string {
|
|
19
|
+
if (providerId === "local") return "Local";
|
|
20
|
+
if (providerId === "google") return "Google";
|
|
21
|
+
if (providerId === "anthropic") return "Anthropic";
|
|
22
|
+
if (providerId === "mistral") return "Mistral Vibe CLI";
|
|
23
|
+
if (providerId === "antigravity") return "Antigravity";
|
|
24
|
+
return "OpenAI";
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
function readDiagnosticString(
|
|
@@ -131,6 +132,7 @@ import {
|
|
|
131
132
|
normalizeReasoningForModelCapabilities,
|
|
132
133
|
type CodexModelCapabilities,
|
|
133
134
|
} from "./core/models/codexModelCapabilities.js";
|
|
135
|
+
import { loadSeededCodexCapabilities } from "./core/models/codexModelsCacheSeed.js";
|
|
134
136
|
import {
|
|
135
137
|
buildDevLaunchNotice,
|
|
136
138
|
buildWorkspaceCommandContext,
|
|
@@ -171,11 +173,18 @@ import {
|
|
|
171
173
|
getProviderRuntime,
|
|
172
174
|
isProviderRouteConfigured,
|
|
173
175
|
isProviderRoutableInCodexa,
|
|
176
|
+
persistProviderDiscovery,
|
|
174
177
|
resolveActiveProviderRoute,
|
|
175
178
|
validateProviderRouteActivation,
|
|
176
179
|
} from "./core/providerRuntime/registry.js";
|
|
177
180
|
import { hasGeminiApiKey, runGeminiDiagnostics } from "./core/providerRuntime/gemini.js";
|
|
178
181
|
import { checkLocalProvider, runLocalDiagnostics, setLocalProviderConfig } from "./core/providerRuntime/local.js";
|
|
182
|
+
import {
|
|
183
|
+
detectVibeActiveModel,
|
|
184
|
+
launchMistralVibeCli,
|
|
185
|
+
resetMistralVibeSession,
|
|
186
|
+
resolveVibeExecutable,
|
|
187
|
+
} from "./core/providerRuntime/mistralVibe.js";
|
|
179
188
|
import { validateAnthropicRoute, ANTHROPIC_ROUTE_SETUP_MESSAGE } from "./core/providerRuntime/anthropic.js";
|
|
180
189
|
import { providerModelsToCodexCapabilities } from "./core/providerRuntime/models.js";
|
|
181
190
|
import {
|
|
@@ -229,25 +238,25 @@ import {
|
|
|
229
238
|
submitPlanFeedback,
|
|
230
239
|
type PlanFlowState,
|
|
231
240
|
} from "./session/planFlow.js";
|
|
232
|
-
import { AuthPanel } from "./ui/AuthPanel.js";
|
|
233
|
-
import { BackendPicker } from "./ui/BackendPicker.js";
|
|
234
|
-
import { measureBottomComposerRows, MemoizedBottomComposer } from "./ui/BottomComposer.js";
|
|
241
|
+
import { AuthPanel } from "./ui/panels/AuthPanel.js";
|
|
242
|
+
import { BackendPicker } from "./ui/panels/BackendPicker.js";
|
|
243
|
+
import { measureBottomComposerRows, MemoizedBottomComposer } from "./ui/chrome/BottomComposer.js";
|
|
235
244
|
import { resolveStartupHeaderMode, useTerminalViewport } from "./ui/layout.js";
|
|
236
|
-
import { ModelPickerScreen } from "./ui/ModelPickerScreen.js";
|
|
237
|
-
import { ModePicker } from "./ui/ModePicker.js";
|
|
238
|
-
import { PlanActionPicker, type PlanActionValue, measurePlanActionPickerRows } from "./ui/PlanActionPicker.js";
|
|
239
|
-
import { PermissionsPanel, type PermissionsPanelAction } from "./ui/PermissionsPanel.js";
|
|
240
|
-
import { ProviderPicker } from "./ui/ProviderPicker.js";
|
|
241
|
-
import { ReasoningPicker } from "./ui/ReasoningPicker.js";
|
|
242
|
-
import { AttachmentImportPanel, type PendingImportFile } from "./ui/AttachmentImportPanel.js";
|
|
243
|
-
import { SelectionPanel } from "./ui/SelectionPanel.js";
|
|
244
|
-
import { SettingsPanel } from "./ui/SettingsPanel.js";
|
|
245
|
-
import { UpdatePromptPanel } from "./ui/UpdatePromptPanel.js";
|
|
246
|
-
import { measureTextEntryPanelRows, TextEntryPanel } from "./ui/TextEntryPanel.js";
|
|
247
|
-
import { ThemePicker } from "./ui/ThemePicker.js";
|
|
248
|
-
import { getFocusTargetForScreen, FOCUS_IDS } from "./ui/focus.js";
|
|
245
|
+
import { ModelPickerScreen } from "./ui/panels/ModelPickerScreen.js";
|
|
246
|
+
import { ModePicker } from "./ui/panels/ModePicker.js";
|
|
247
|
+
import { PlanActionPicker, type PlanActionValue, measurePlanActionPickerRows } from "./ui/panels/PlanActionPicker.js";
|
|
248
|
+
import { PermissionsPanel, type PermissionsPanelAction } from "./ui/panels/PermissionsPanel.js";
|
|
249
|
+
import { ProviderPicker } from "./ui/panels/ProviderPicker.js";
|
|
250
|
+
import { ReasoningPicker } from "./ui/panels/ReasoningPicker.js";
|
|
251
|
+
import { AttachmentImportPanel, type PendingImportFile } from "./ui/panels/AttachmentImportPanel.js";
|
|
252
|
+
import { SelectionPanel } from "./ui/panels/SelectionPanel.js";
|
|
253
|
+
import { SettingsPanel } from "./ui/panels/SettingsPanel.js";
|
|
254
|
+
import { UpdatePromptPanel } from "./ui/panels/UpdatePromptPanel.js";
|
|
255
|
+
import { measureTextEntryPanelRows, TextEntryPanel } from "./ui/panels/TextEntryPanel.js";
|
|
256
|
+
import { ThemePicker } from "./ui/panels/ThemePicker.js";
|
|
257
|
+
import { getFocusTargetForScreen, FOCUS_IDS } from "./ui/input/focus.js";
|
|
249
258
|
import { ThemeProvider, THEMES } from "./ui/theme.js";
|
|
250
|
-
import { buildActiveRuntimeDisplay, runtimeDisplayToSummary } from "./ui/runtimeDisplay.js";
|
|
259
|
+
import { buildActiveRuntimeDisplay, runtimeDisplayToSummary } from "./ui/render/runtimeDisplay.js";
|
|
251
260
|
import {
|
|
252
261
|
cancelThemeSelection,
|
|
253
262
|
commitThemeSelection,
|
|
@@ -257,9 +266,9 @@ import {
|
|
|
257
266
|
type ThemeSelectionState,
|
|
258
267
|
} from "./ui/themeFlow.js";
|
|
259
268
|
import { isBusy as isUiBusy } from "./session/types.js";
|
|
260
|
-
import { AppShell } from "./ui/AppShell.js";
|
|
261
|
-
import { TranscriptShell } from "./ui/TranscriptShell.js";
|
|
262
|
-
import type { RuntimeAvailability } from "./ui/RuntimeStatusBar.js";
|
|
269
|
+
import { AppShell } from "./ui/chrome/AppShell.js";
|
|
270
|
+
import { TranscriptShell } from "./ui/timeline/TranscriptShell.js";
|
|
271
|
+
import type { RuntimeAvailability } from "./ui/chrome/RuntimeStatusBar.js";
|
|
263
272
|
import { checkForUpdates, formatLocalDevUpdateStatus, formatUpdateInstructions, shouldRunStartupUpdateCheck, type UpdateCheckResult } from "./core/version/updateCheck.js";
|
|
264
273
|
import { isLocalDevChannel } from "./core/version/channel.js";
|
|
265
274
|
import {
|
|
@@ -319,7 +328,7 @@ function createProviderMigrationNoticeEvent(
|
|
|
319
328
|
createdAt: Date.now(),
|
|
320
329
|
title: sanitizeTerminalOutput("Provider migrated"),
|
|
321
330
|
content: sanitizeTerminalOutput(
|
|
322
|
-
`${formatRuntimeProviderLabel(notice.deprecatedProviderId as ProviderId)} provider is no longer supported. Reverted to ${resolvedProviderLabel}.`,
|
|
331
|
+
`${formatRuntimeProviderLabel(notice.deprecatedProviderId as ProviderId)} provider is no longer supported. Reverted to ${resolvedProviderLabel}.`,
|
|
323
332
|
{ preserveTabs: false, tabSize: 2 },
|
|
324
333
|
),
|
|
325
334
|
};
|
|
@@ -483,8 +492,14 @@ export function App({ launchArgs }: AppProps) {
|
|
|
483
492
|
const [authStatusBusy, setAuthStatusBusy] = useState(false);
|
|
484
493
|
// Running character total across the conversation — used to estimate token usage
|
|
485
494
|
const [conversationChars, setConversationChars] = useState(0);
|
|
486
|
-
|
|
495
|
+
// Seeded synchronously from local caches (codex's models_cache.json or the
|
|
496
|
+
// persisted last-good discovery) so the model picker opens instantly with
|
|
497
|
+
// real models; live discovery replaces this in the background.
|
|
498
|
+
const [modelCapabilities, setModelCapabilities] = useState<CodexModelCapabilities | null>(() => loadSeededCodexCapabilities());
|
|
487
499
|
const [modelCapabilitiesBusy, setModelCapabilitiesBusy] = useState(false);
|
|
500
|
+
// True while a provider route switch is validating (subprocess probes);
|
|
501
|
+
// drives the model picker's loading state for non-openai providers.
|
|
502
|
+
const [routeSwitchBusy, setRouteSwitchBusy] = useState(false);
|
|
488
503
|
const [activeContextMetadata, setActiveContextMetadata] = useState<ModelContextMetadata | null>(null);
|
|
489
504
|
const { stdout } = useStdout();
|
|
490
505
|
const { stdin } = useStdin();
|
|
@@ -658,6 +673,7 @@ export function App({ launchArgs }: AppProps) {
|
|
|
658
673
|
setLocalProviderConfig(providerWorkspaceConfig.providers?.local);
|
|
659
674
|
return buildProviderRegistry({
|
|
660
675
|
activeModel: model,
|
|
676
|
+
workspaceRoot,
|
|
661
677
|
workspaceConfig: providerWorkspaceConfig,
|
|
662
678
|
diagnostics: providerDiagnosticsRef.current,
|
|
663
679
|
routeErrors: providerRouteErrorsRef.current,
|
|
@@ -667,7 +683,7 @@ export function App({ launchArgs }: AppProps) {
|
|
|
667
683
|
// updates mutate providerDiagnosticsRef/providerRouteErrorsRef (refs, not state)
|
|
668
684
|
// and then increment the nonce to trigger a re-read of those refs.
|
|
669
685
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
670
|
-
[model, providerWorkspaceConfig, registryNonce],
|
|
686
|
+
[model, providerWorkspaceConfig, registryNonce, workspaceRoot],
|
|
671
687
|
);
|
|
672
688
|
const workspaceDefaultProvider = useMemo(
|
|
673
689
|
() => providerRegistry.find((provider) => provider.isDefault) ?? providerRegistry[0] ?? null,
|
|
@@ -717,7 +733,10 @@ export function App({ launchArgs }: AppProps) {
|
|
|
717
733
|
const modelPickerDiscovery = useMemo(() => {
|
|
718
734
|
if (modelPickerProviderId === "openai") return null;
|
|
719
735
|
return discoverProviderModels(modelPickerProviderId);
|
|
720
|
-
|
|
736
|
+
// registryNonce is intentionally included: route validation discovers models
|
|
737
|
+
// as a side effect and bumps the nonce so an open picker re-reads them.
|
|
738
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
739
|
+
}, [modelPickerProviderId, registryNonce]);
|
|
721
740
|
const providerModelCapabilities = useMemo(() => {
|
|
722
741
|
if (!modelPickerDiscovery) return null;
|
|
723
742
|
return providerModelsToCodexCapabilities(modelPickerDiscovery.models, activeProviderRoute.modelId);
|
|
@@ -1568,6 +1587,9 @@ export function App({ launchArgs }: AppProps) {
|
|
|
1568
1587
|
providerRouteErrorsRef.current["anthropic"] = result.message ?? ANTHROPIC_ROUTE_SETUP_MESSAGE;
|
|
1569
1588
|
} else {
|
|
1570
1589
|
delete providerRouteErrorsRef.current["anthropic"];
|
|
1590
|
+
// Persist the freshly discovered catalog so newly released Claude
|
|
1591
|
+
// models replace the stale on-disk cache without a manual refresh.
|
|
1592
|
+
persistProviderDiscovery(discoverProviderModels("anthropic"));
|
|
1571
1593
|
}
|
|
1572
1594
|
} catch {
|
|
1573
1595
|
// Best-effort probe — failures are surfaced only when the user activates the route.
|
|
@@ -1993,6 +2015,12 @@ export function App({ launchArgs }: AppProps) {
|
|
|
1993
2015
|
}
|
|
1994
2016
|
|
|
1995
2017
|
modelSelectionInFlightRef.current = true;
|
|
2018
|
+
// Reflect the target provider in the model picker immediately: route
|
|
2019
|
+
// validation below can take seconds (subprocess probes), and until it
|
|
2020
|
+
// persists the new active route, modelPickerProviderId would otherwise
|
|
2021
|
+
// keep resolving to the stale route.
|
|
2022
|
+
setPendingRouteProviderId(providerId);
|
|
2023
|
+
setRouteSwitchBusy(true);
|
|
1996
2024
|
traceInputDebug("model_selection_app_start", getInputDebugSnapshot({
|
|
1997
2025
|
handler: "setModelAndReasoningWithNotice",
|
|
1998
2026
|
model: nextModel,
|
|
@@ -2062,6 +2090,10 @@ export function App({ launchArgs }: AppProps) {
|
|
|
2062
2090
|
model: nextModel,
|
|
2063
2091
|
reasoningLevel: normalizedReasoning,
|
|
2064
2092
|
}));
|
|
2093
|
+
if (providerId === "mistral" && activeProviderRoute.providerId !== "mistral") {
|
|
2094
|
+
// Switching to Vibe starts a fresh CLI conversation instead of resuming a stale one.
|
|
2095
|
+
resetMistralVibeSession(workspaceRoot);
|
|
2096
|
+
}
|
|
2065
2097
|
persistActiveRoute(providerId, nextModel, normalizedReasoning, validation.backendKind, geminiSelection);
|
|
2066
2098
|
setPendingRouteProviderId(null);
|
|
2067
2099
|
traceInputDebug("model_selection_app_success", getInputDebugSnapshot({
|
|
@@ -2079,8 +2111,10 @@ export function App({ launchArgs }: AppProps) {
|
|
|
2079
2111
|
reasoning: nextReasoning,
|
|
2080
2112
|
error: message,
|
|
2081
2113
|
}));
|
|
2114
|
+
setPendingRouteProviderId(null);
|
|
2082
2115
|
appendErrorEvent("Model selection failed", message);
|
|
2083
2116
|
} finally {
|
|
2117
|
+
setRouteSwitchBusy(false);
|
|
2084
2118
|
modelSelectionInFlightRef.current = false;
|
|
2085
2119
|
returnToChatMode("selection");
|
|
2086
2120
|
}
|
|
@@ -2269,7 +2303,11 @@ export function App({ launchArgs }: AppProps) {
|
|
|
2269
2303
|
}, [appendSystemEvent, busy]);
|
|
2270
2304
|
|
|
2271
2305
|
const openProviderPicker = useCallback(() => {
|
|
2272
|
-
|
|
2306
|
+
// Mid route switch, keep the pending id so initialProviderId highlights
|
|
2307
|
+
// the provider being activated instead of the stale route.
|
|
2308
|
+
if (!modelSelectionInFlightRef.current) {
|
|
2309
|
+
setPendingRouteProviderId(null);
|
|
2310
|
+
}
|
|
2273
2311
|
const gate = guardConfigMutation("backend", busy);
|
|
2274
2312
|
if (!gate.allowed) {
|
|
2275
2313
|
appendSystemEvent("Busy", gate.message ?? "Finish the current run before opening providers.");
|
|
@@ -2277,6 +2315,31 @@ export function App({ launchArgs }: AppProps) {
|
|
|
2277
2315
|
}
|
|
2278
2316
|
|
|
2279
2317
|
setScreen("provider-picker");
|
|
2318
|
+
providerDiagnosticsRef.current.mistral = {
|
|
2319
|
+
...providerDiagnosticsRef.current.mistral,
|
|
2320
|
+
selectedModel: detectVibeActiveModel({ cwd: workspaceRoot }).modelId,
|
|
2321
|
+
availabilityStatus: "checking",
|
|
2322
|
+
};
|
|
2323
|
+
void resolveVibeExecutable({ cwd: workspaceRoot }).then((resolvedCommand) => {
|
|
2324
|
+
if (!isMountedRef.current) return;
|
|
2325
|
+
const modelDetection = detectVibeActiveModel({ cwd: workspaceRoot });
|
|
2326
|
+
providerDiagnosticsRef.current.mistral = {
|
|
2327
|
+
resolvedCommand,
|
|
2328
|
+
selectedModel: modelDetection.modelId,
|
|
2329
|
+
modelSource: modelDetection.source,
|
|
2330
|
+
configPath: modelDetection.configPath,
|
|
2331
|
+
availabilityStatus: resolvedCommand ? "available" : "unavailable",
|
|
2332
|
+
};
|
|
2333
|
+
setRegistryNonce((n) => n + 1);
|
|
2334
|
+
}).catch(() => {
|
|
2335
|
+
if (!isMountedRef.current) return;
|
|
2336
|
+
providerDiagnosticsRef.current.mistral = {
|
|
2337
|
+
selectedModel: detectVibeActiveModel({ cwd: workspaceRoot }).modelId,
|
|
2338
|
+
resolvedCommand: null,
|
|
2339
|
+
availabilityStatus: "unavailable",
|
|
2340
|
+
};
|
|
2341
|
+
setRegistryNonce((n) => n + 1);
|
|
2342
|
+
});
|
|
2280
2343
|
markProviderAvailability("local", "checking", "provider-picker-open");
|
|
2281
2344
|
void checkLocalProvider({ override: providerWorkspaceConfig.providers?.local }).then((result) => {
|
|
2282
2345
|
if (!isMountedRef.current) return;
|
|
@@ -2290,7 +2353,7 @@ export function App({ launchArgs }: AppProps) {
|
|
|
2290
2353
|
}
|
|
2291
2354
|
setRegistryNonce((n) => n + 1);
|
|
2292
2355
|
}).catch(() => undefined);
|
|
2293
|
-
}, [appendSystemEvent, busy, markProviderAvailability, providerWorkspaceConfig.providers]);
|
|
2356
|
+
}, [appendSystemEvent, busy, markProviderAvailability, providerWorkspaceConfig.providers, workspaceRoot]);
|
|
2294
2357
|
|
|
2295
2358
|
const setWorkspaceDefaultProviderWithNotice = useCallback((providerId: ProviderId) => {
|
|
2296
2359
|
const provider = findProvider(providerRegistry, providerId);
|
|
@@ -2307,7 +2370,9 @@ export function App({ launchArgs }: AppProps) {
|
|
|
2307
2370
|
const routeConfigured = isProviderRouteConfigured(providerId);
|
|
2308
2371
|
appendSystemEvent(
|
|
2309
2372
|
"Provider default updated",
|
|
2310
|
-
provider.routeMode === "
|
|
2373
|
+
provider.routeMode === "launch-only"
|
|
2374
|
+
? `${provider.displayName} is now the workspace default external CLI. Active chat route remains ${activeRouteProvider?.displayName ?? "OpenAI"} / ${model}.`
|
|
2375
|
+
: provider.routeMode === "in-codexa" && routeConfigured
|
|
2311
2376
|
? `${provider.displayName} is now the workspace default provider. Active chat route remains ${activeRouteProvider?.displayName ?? "OpenAI"} / ${model}.`
|
|
2312
2377
|
: `${provider.displayName} is set as the workspace default, but in-Codexa routing is not configured yet. Active chat route remains ${activeRouteProvider?.displayName ?? "OpenAI"} / ${model}.`,
|
|
2313
2378
|
);
|
|
@@ -2466,6 +2531,7 @@ export function App({ launchArgs }: AppProps) {
|
|
|
2466
2531
|
cwd: workspaceRoot,
|
|
2467
2532
|
localConfig: providerId === "local" ? providerWorkspaceConfig.providers?.local : undefined,
|
|
2468
2533
|
}).then((discovery) => {
|
|
2534
|
+
persistProviderDiscovery(discovery);
|
|
2469
2535
|
if (discovery.diagnostics) {
|
|
2470
2536
|
providerDiagnosticsRef.current[providerId] = discovery.diagnostics as Record<string, string | number | boolean | null>;
|
|
2471
2537
|
}
|
|
@@ -2557,10 +2623,10 @@ export function App({ launchArgs }: AppProps) {
|
|
|
2557
2623
|
setScreen("main");
|
|
2558
2624
|
appendSystemEvent(
|
|
2559
2625
|
"Provider launch",
|
|
2560
|
-
`Suspending Codexa and launching ${provider.displayName}. Codexa will resume when the external CLI exits.`,
|
|
2626
|
+
`Suspending Codexa and launching ${provider.displayName}${providerId === "mistral" ? ` / ${provider.currentModel}` : ""}. Codexa will resume when the external CLI exits.`,
|
|
2561
2627
|
);
|
|
2562
2628
|
|
|
2563
|
-
|
|
2629
|
+
const launchOptions = {
|
|
2564
2630
|
cwd: workspaceRoot,
|
|
2565
2631
|
stdin,
|
|
2566
2632
|
beforeLaunch: () => {
|
|
@@ -2570,9 +2636,18 @@ export function App({ launchArgs }: AppProps) {
|
|
|
2570
2636
|
afterLaunch: () => {
|
|
2571
2637
|
terminalControl.setMouseReporting(effectiveMouseCapture, "src/app.tsx:providerLaunch.restoreMouse");
|
|
2572
2638
|
},
|
|
2573
|
-
}
|
|
2639
|
+
};
|
|
2640
|
+
const launchPromise = providerId === "mistral"
|
|
2641
|
+
? launchMistralVibeCli(provider, launchOptions)
|
|
2642
|
+
: launchProviderCli(provider, launchOptions);
|
|
2643
|
+
|
|
2644
|
+
void launchPromise.then((result) => {
|
|
2574
2645
|
if (!isMountedRef.current) return;
|
|
2575
|
-
|
|
2646
|
+
if (providerId === "mistral" && (result.status === "missing-command" || result.status === "spawn-error")) {
|
|
2647
|
+
appendErrorEvent("Mistral Vibe launch failed", result.message);
|
|
2648
|
+
} else {
|
|
2649
|
+
appendSystemEvent("Provider launch", result.message);
|
|
2650
|
+
}
|
|
2576
2651
|
}).catch((error) => {
|
|
2577
2652
|
if (!isMountedRef.current) return;
|
|
2578
2653
|
const message = error instanceof Error ? error.message : "Provider launch failed.";
|
|
@@ -2599,7 +2674,11 @@ export function App({ launchArgs }: AppProps) {
|
|
|
2599
2674
|
]);
|
|
2600
2675
|
|
|
2601
2676
|
const openModelPicker = useCallback(() => {
|
|
2602
|
-
|
|
2677
|
+
// While a route switch is validating, keep the pending id so the picker
|
|
2678
|
+
// shows the target provider instead of snapping back to the stale route.
|
|
2679
|
+
if (!modelSelectionInFlightRef.current) {
|
|
2680
|
+
setPendingRouteProviderId(null);
|
|
2681
|
+
}
|
|
2603
2682
|
traceInputDebug("model_picker_open_request", getInputDebugSnapshot({
|
|
2604
2683
|
handler: "openModelPicker",
|
|
2605
2684
|
currentScreen: screen,
|
|
@@ -3121,6 +3200,7 @@ export function App({ launchArgs }: AppProps) {
|
|
|
3121
3200
|
activeTurnIdRef.current = null;
|
|
3122
3201
|
activeRunLifecycleRef.current = null;
|
|
3123
3202
|
activeRunTimingRef.current = null;
|
|
3203
|
+
resetMistralVibeSession(workspaceRoot);
|
|
3124
3204
|
setPlanFlow(resetPlanFlow());
|
|
3125
3205
|
renderDebug.traceEvent("terminal", "clearReactStateRequested", {
|
|
3126
3206
|
clearGeneration,
|
|
@@ -3139,7 +3219,7 @@ export function App({ launchArgs }: AppProps) {
|
|
|
3139
3219
|
liveInkInstanceResolved: Boolean(inkInstance),
|
|
3140
3220
|
});
|
|
3141
3221
|
}
|
|
3142
|
-
}, [cancelActiveRun, clearFrameBoundaryController, inkInstance, launchContext, providerWorkspaceConfig, resetToHomeScreen, sessionState.clearEpoch, stdout.columns, terminalControl]);
|
|
3222
|
+
}, [cancelActiveRun, clearFrameBoundaryController, inkInstance, launchContext, providerWorkspaceConfig, resetToHomeScreen, sessionState.clearEpoch, stdout.columns, terminalControl, workspaceRoot]);
|
|
3143
3223
|
|
|
3144
3224
|
const handleShellExecute = useCallback((command: string) => {
|
|
3145
3225
|
const safeCommand = sanitizeTerminalInput(command).trim();
|
|
@@ -4174,13 +4254,13 @@ export function App({ launchArgs }: AppProps) {
|
|
|
4174
4254
|
case "diagnose_providers": {
|
|
4175
4255
|
const lines: string[] = ["Provider CLI diagnostics:"];
|
|
4176
4256
|
const diags = providerDiagnosticsRef.current;
|
|
4177
|
-
const providerIds = ["openai", "anthropic", "local", "antigravity"] as const;
|
|
4178
|
-
const labels: Record<string, string> = {
|
|
4179
|
-
openai: "OpenAI/Codex",
|
|
4180
|
-
anthropic: "Anthropic/Claude",
|
|
4181
|
-
local: "Local OpenAI-compatible",
|
|
4182
|
-
antigravity: "Antigravity CLI",
|
|
4183
|
-
};
|
|
4257
|
+
const providerIds = ["openai", "anthropic", "local", "antigravity"] as const;
|
|
4258
|
+
const labels: Record<string, string> = {
|
|
4259
|
+
openai: "OpenAI/Codex",
|
|
4260
|
+
anthropic: "Anthropic/Claude",
|
|
4261
|
+
local: "Local OpenAI-compatible",
|
|
4262
|
+
antigravity: "Antigravity CLI",
|
|
4263
|
+
};
|
|
4184
4264
|
for (const id of providerIds) {
|
|
4185
4265
|
const diag = diags[id];
|
|
4186
4266
|
lines.push(`\n ${labels[id] ?? id}:`);
|
|
@@ -4705,7 +4785,8 @@ export function App({ launchArgs }: AppProps) {
|
|
|
4705
4785
|
currentModel={modelPickerCurrentModel}
|
|
4706
4786
|
currentReasoning={modelPickerCurrentReasoning}
|
|
4707
4787
|
activeProviderLabel={modelPickerProviderLabel}
|
|
4708
|
-
isLoading={
|
|
4788
|
+
isLoading={modelPickerModels.length === 0
|
|
4789
|
+
&& ((modelPickerProviderId === "openai" && modelCapabilitiesBusy) || routeSwitchBusy)}
|
|
4709
4790
|
emptyMessage={modelPickerEmptyMessage}
|
|
4710
4791
|
onSelect={(m, r, geminiSelection) => {
|
|
4711
4792
|
if (pendingRouteProviderId && pendingRouteProviderId !== activeProviderRoute.providerId) {
|
package/src/commands/handler.ts
CHANGED
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
AUTH_PREFERENCES,
|
|
19
19
|
AVAILABLE_BACKENDS,
|
|
20
20
|
AVAILABLE_MODELS,
|
|
21
|
-
AVAILABLE_REASONING_LEVELS,
|
|
22
21
|
AVAILABLE_THEMES,
|
|
23
22
|
BUSY_LOADER_SETTING_VALUES,
|
|
24
23
|
WORKSPACE_DISPLAY_MODES,
|
|
@@ -150,10 +149,6 @@ function expandReasoningAliases(arg: string): string {
|
|
|
150
149
|
return normalized === "extra high" ? "xhigh" : normalized;
|
|
151
150
|
}
|
|
152
151
|
|
|
153
|
-
function isKnownFallbackReasoning(value: string): boolean {
|
|
154
|
-
return AVAILABLE_REASONING_LEVELS.some((item) => item.id === value);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
152
|
function simplePolicySetter<T extends string>(
|
|
158
153
|
rest: string,
|
|
159
154
|
normalizedRest: string,
|
|
@@ -520,17 +515,11 @@ export function handleCommand(text: string, context: CommandContext): CommandRes
|
|
|
520
515
|
message: `Unknown reasoning level for ${context.runtime.model}: ${arg}. Valid: ${valid}`,
|
|
521
516
|
};
|
|
522
517
|
}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
};
|
|
529
|
-
}
|
|
530
|
-
return {
|
|
531
|
-
action: "unknown",
|
|
532
|
-
message: `Unknown reasoning level: ${arg}. Runtime reasoning metadata is unavailable for ${context.runtime.model}.`,
|
|
533
|
-
};
|
|
518
|
+
return {
|
|
519
|
+
action: "reasoning",
|
|
520
|
+
value: normalized,
|
|
521
|
+
message: `Reasoning level switched to ${formatReasoningLabel(normalized)} (runtime metadata unavailable; unverified until runtime)`,
|
|
522
|
+
};
|
|
534
523
|
}
|
|
535
524
|
|
|
536
525
|
case "plan": {
|
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 APP_VERSION = "1.0.
|
|
2
|
+
export const BUILD_COMMIT = "aaffb655176a0acb1bc7800ddbb3e6402e5933f4" as const;
|
|
3
|
+
export const APP_VERSION = "1.0.5" as const;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { type ChildProcess } from "child_process";
|
|
2
2
|
import { APP_NAME, APP_VERSION, DEFAULT_MODEL, LEGACY_FALLBACK_MODELS, formatReasoningLabel } from "../../config/settings.js";
|
|
3
3
|
import { resolveCodexExecutable, spawnCodexProcess } from "../executables/codexExecutable.js";
|
|
4
|
+
import { loadSeededCodexCapabilities } from "./codexModelsCacheSeed.js";
|
|
5
|
+
import { saveCachedProviderModels } from "./providerModelCache.js";
|
|
6
|
+
import type { ProviderModel } from "../providerRuntime/types.js";
|
|
4
7
|
|
|
5
8
|
export type ModelCapabilitySource = "runtime" | "fallback";
|
|
6
9
|
export type ModelCapabilityStatus = "ready" | "fallback";
|
|
@@ -47,6 +50,8 @@ export interface GetCodexModelCapabilitiesOptions extends DiscoverCodexModelCapa
|
|
|
47
50
|
ttlMs?: number;
|
|
48
51
|
resolveExecutable?: typeof resolveCodexExecutable;
|
|
49
52
|
discover?: typeof discoverCodexModelCapabilities;
|
|
53
|
+
seed?: typeof loadSeededCodexCapabilities;
|
|
54
|
+
persist?: typeof persistCodexModelCapabilities;
|
|
50
55
|
}
|
|
51
56
|
|
|
52
57
|
interface JsonRpcResponse {
|
|
@@ -439,14 +444,36 @@ export async function discoverCodexModelCapabilities(
|
|
|
439
444
|
});
|
|
440
445
|
}
|
|
441
446
|
|
|
442
|
-
//
|
|
443
|
-
//
|
|
447
|
+
// Persist a successful live discovery so the next launch can seed the model
|
|
448
|
+
// picker without spawning the codex app-server. Best-effort by design.
|
|
449
|
+
export function persistCodexModelCapabilities(capabilities: CodexModelCapabilities): void {
|
|
450
|
+
const models: ProviderModel[] = capabilities.models
|
|
451
|
+
.filter((capability) => !capability.hidden)
|
|
452
|
+
.map((capability) => ({
|
|
453
|
+
id: capability.id,
|
|
454
|
+
modelId: capability.model,
|
|
455
|
+
label: capability.label,
|
|
456
|
+
description: capability.description,
|
|
457
|
+
defaultReasoningLevel: capability.defaultReasoningLevel,
|
|
458
|
+
supportedReasoningLevels: capability.supportedReasoningLevels,
|
|
459
|
+
source: "discovered",
|
|
460
|
+
}));
|
|
461
|
+
saveCachedProviderModels("openai", { discoveredAt: capabilities.discoveredAt, models });
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Cache chain: TTL-based in-memory cache → live discovery → seeded local caches
|
|
465
|
+
// (codex's own models_cache.json / Codexa's last-good discovery) → static
|
|
466
|
+
// fallback model list. Failed discoveries are evicted from the in-memory cache
|
|
467
|
+
// so retries are possible even when a seed satisfied the request.
|
|
444
468
|
export async function getCodexModelCapabilities(
|
|
445
469
|
options: GetCodexModelCapabilitiesOptions = {},
|
|
446
470
|
): Promise<CodexModelCapabilities> {
|
|
447
471
|
const now = options.now?.() ?? Date.now();
|
|
448
472
|
const ttlMs = options.ttlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
473
|
+
const seed = options.seed ?? loadSeededCodexCapabilities;
|
|
474
|
+
const persist = options.persist ?? persistCodexModelCapabilities;
|
|
449
475
|
let executable: string | null = null;
|
|
476
|
+
let liveDiscoverySucceeded = false;
|
|
450
477
|
|
|
451
478
|
try {
|
|
452
479
|
executable = options.executable ?? await (options.resolveExecutable ?? resolveCodexExecutable)();
|
|
@@ -462,7 +489,25 @@ export async function getCodexModelCapabilities(
|
|
|
462
489
|
includeHidden: options.includeHidden,
|
|
463
490
|
timeoutMs: options.timeoutMs,
|
|
464
491
|
now: () => now,
|
|
465
|
-
}).
|
|
492
|
+
}).then((discovered) => {
|
|
493
|
+
liveDiscoverySucceeded = true;
|
|
494
|
+
try {
|
|
495
|
+
persist(discovered);
|
|
496
|
+
} catch {
|
|
497
|
+
// Persistence must never fail a successful discovery.
|
|
498
|
+
}
|
|
499
|
+
return discovered;
|
|
500
|
+
}).catch((error) => {
|
|
501
|
+
try {
|
|
502
|
+
const seeded = seed();
|
|
503
|
+
if (seeded) {
|
|
504
|
+
return seeded;
|
|
505
|
+
}
|
|
506
|
+
} catch {
|
|
507
|
+
// Seed read failures fall through to the static list.
|
|
508
|
+
}
|
|
509
|
+
return createFallbackModelCapabilities(error, { discoveredAt: now, executable });
|
|
510
|
+
});
|
|
466
511
|
|
|
467
512
|
capabilityCache.set(cacheKey, {
|
|
468
513
|
expiresAt: now + ttlMs,
|
|
@@ -470,12 +515,20 @@ export async function getCodexModelCapabilities(
|
|
|
470
515
|
});
|
|
471
516
|
|
|
472
517
|
const result = await promise;
|
|
473
|
-
if (
|
|
518
|
+
if (!liveDiscoverySucceeded) {
|
|
474
519
|
capabilityCache.delete(cacheKey);
|
|
475
520
|
}
|
|
476
521
|
|
|
477
522
|
return result;
|
|
478
523
|
} catch (error) {
|
|
524
|
+
try {
|
|
525
|
+
const seeded = seed();
|
|
526
|
+
if (seeded) {
|
|
527
|
+
return seeded;
|
|
528
|
+
}
|
|
529
|
+
} catch {
|
|
530
|
+
// Seed read failures fall through to the static list.
|
|
531
|
+
}
|
|
479
532
|
return createFallbackModelCapabilities(error, { discoveredAt: now, executable });
|
|
480
533
|
}
|
|
481
534
|
}
|