@golba98/codexa 1.0.4 → 1.0.6
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 +6 -10
- package/package.json +1 -1
- package/src/app.tsx +188 -121
- package/src/commands/handler.ts +11 -20
- package/src/config/appVersion.ts +65 -0
- package/src/config/buildInfo.ts +2 -2
- package/src/config/settings.ts +5 -1
- package/src/config/updateCheckCache.ts +18 -8
- package/src/core/models/codexModelCapabilities.ts +57 -4
- package/src/core/models/codexModelsCacheSeed.ts +153 -0
- package/src/core/models/providerModelCache.ts +90 -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 +257 -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 +93 -44
- package/src/core/providerRuntime/types.ts +1 -0
- package/src/core/version/packageManager.ts +119 -0
- package/src/core/version/updateCheck.ts +15 -9
- package/src/index.tsx +23 -9
- 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} +15 -13
- package/src/ui/{UpdateAvailableCard.tsx → chrome/UpdateAvailableCard.tsx} +6 -5
- package/src/ui/{commandNormalize.ts → input/commandNormalize.ts} +1 -1
- package/src/ui/{focus.ts → input/focus.ts} +1 -1
- package/src/ui/{inputBuffer.ts → input/inputBuffer.ts} +3 -3
- package/src/ui/{slashCommands.ts → input/slashCommands.ts} +1 -0
- 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} +54 -44
- 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/{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/README.md
CHANGED
|
@@ -577,7 +577,7 @@ npm install -g @golba98/codexa@latest
|
|
|
577
577
|
|
|
578
578
|
### Update notice does not appear
|
|
579
579
|
|
|
580
|
-
Codexa checks for updates on
|
|
580
|
+
Codexa checks npm for updates on every interactive startup. The local cache at `~/.codexa-update-check.json` is only used as a best-effort fallback if npm is temporarily unavailable. The update notice appears when the npm registry `latest` version is newer than the running version.
|
|
581
581
|
|
|
582
582
|
Published npm versions are immutable. Versions before the fixed update checker may not show update notices even when a newer package exists. If in doubt, update directly:
|
|
583
583
|
|
|
@@ -588,9 +588,8 @@ npm install -g @golba98/codexa@latest
|
|
|
588
588
|
If you expect a notice but don't see one:
|
|
589
589
|
|
|
590
590
|
1. Check the current registry state: `npm view @golba98/codexa dist-tags --json`
|
|
591
|
-
2.
|
|
592
|
-
3.
|
|
593
|
-
4. Update checks are disabled for local dev builds (`codexa-dev` / `cxd`)
|
|
591
|
+
2. Force an explicit check in-app: `/update check`
|
|
592
|
+
3. Update checks are disabled for local dev builds (`codexa-dev` / `cxd`)
|
|
594
593
|
|
|
595
594
|
| Symptom | Fix |
|
|
596
595
|
|---------|-----|
|
|
@@ -606,11 +605,8 @@ If you expect a notice but don't see one:
|
|
|
606
605
|
|
|
607
606
|
See [CHANGELOG.md](CHANGELOG.md) for the full release history.
|
|
608
607
|
|
|
609
|
-
**Current release: v1.0.
|
|
608
|
+
**Current release: v1.0.6**
|
|
610
609
|
|
|
611
|
-
v1.0.
|
|
612
|
-
- Older installed versions can detect when npm `latest` is newer
|
|
613
|
-
- `/update check` bypasses stale cache and reports a useful status
|
|
614
|
-
- Failed registry lookups do not get cached as “up to date”
|
|
610
|
+
v1.0.6 checks npm on every interactive startup and shows an update prompt as soon as Codexa is idle. The prompt offers the correct update command for the package manager that installed Codexa.
|
|
615
611
|
|
|
616
|
-
> Versions before
|
|
612
|
+
> Versions before v1.0.6 can wait for a cached result before discovering a new npm release. Update directly with `npm install -g @golba98/codexa@latest` if needed.
|
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,13 +266,14 @@ 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";
|
|
273
|
+
import { detectGlobalPackageManager, getUpdateCommand } from "./core/version/packageManager.js";
|
|
264
274
|
import { isLocalDevChannel } from "./core/version/channel.js";
|
|
265
275
|
import {
|
|
266
|
-
|
|
276
|
+
isCacheForRunningVersion,
|
|
267
277
|
loadUpdateCheckCache,
|
|
268
278
|
saveUpdateCheckCache,
|
|
269
279
|
} from "./config/updateCheckCache.js";
|
|
@@ -319,7 +329,7 @@ function createProviderMigrationNoticeEvent(
|
|
|
319
329
|
createdAt: Date.now(),
|
|
320
330
|
title: sanitizeTerminalOutput("Provider migrated"),
|
|
321
331
|
content: sanitizeTerminalOutput(
|
|
322
|
-
`${formatRuntimeProviderLabel(notice.deprecatedProviderId as ProviderId)} provider is no longer supported. Reverted to ${resolvedProviderLabel}.`,
|
|
332
|
+
`${formatRuntimeProviderLabel(notice.deprecatedProviderId as ProviderId)} provider is no longer supported. Reverted to ${resolvedProviderLabel}.`,
|
|
323
333
|
{ preserveTabs: false, tabSize: 2 },
|
|
324
334
|
),
|
|
325
335
|
};
|
|
@@ -385,9 +395,6 @@ export function App({ launchArgs }: AppProps) {
|
|
|
385
395
|
? projectInstructionsLoad.instructions
|
|
386
396
|
: null;
|
|
387
397
|
const initialSettings = useRef(loadSettings());
|
|
388
|
-
const skippedUpdateVersionRef = useRef<string | null>(
|
|
389
|
-
initialSettings.current.updateCheck.skippedUpdateVersion ?? null,
|
|
390
|
-
);
|
|
391
398
|
const initialProviderWorkspaceConfig = useRef<ProviderWorkspaceConfig>(loadProviderWorkspaceConfig(workspaceRoot));
|
|
392
399
|
const initialLayeredConfig = useRef<LayeredConfigResult | null>(null);
|
|
393
400
|
if (initialLayeredConfig.current === null) {
|
|
@@ -483,8 +490,14 @@ export function App({ launchArgs }: AppProps) {
|
|
|
483
490
|
const [authStatusBusy, setAuthStatusBusy] = useState(false);
|
|
484
491
|
// Running character total across the conversation — used to estimate token usage
|
|
485
492
|
const [conversationChars, setConversationChars] = useState(0);
|
|
486
|
-
|
|
493
|
+
// Seeded synchronously from local caches (codex's models_cache.json or the
|
|
494
|
+
// persisted last-good discovery) so the model picker opens instantly with
|
|
495
|
+
// real models; live discovery replaces this in the background.
|
|
496
|
+
const [modelCapabilities, setModelCapabilities] = useState<CodexModelCapabilities | null>(() => loadSeededCodexCapabilities());
|
|
487
497
|
const [modelCapabilitiesBusy, setModelCapabilitiesBusy] = useState(false);
|
|
498
|
+
// True while a provider route switch is validating (subprocess probes);
|
|
499
|
+
// drives the model picker's loading state for non-openai providers.
|
|
500
|
+
const [routeSwitchBusy, setRouteSwitchBusy] = useState(false);
|
|
488
501
|
const [activeContextMetadata, setActiveContextMetadata] = useState<ModelContextMetadata | null>(null);
|
|
489
502
|
const { stdout } = useStdout();
|
|
490
503
|
const { stdin } = useStdin();
|
|
@@ -535,6 +548,9 @@ export function App({ launchArgs }: AppProps) {
|
|
|
535
548
|
const [planFlow, setPlanFlow] = useState<PlanFlowState>(createInitialPlanFlowState);
|
|
536
549
|
const [initialRevisionText, setInitialRevisionText] = useState("");
|
|
537
550
|
const [updateCheckResult, setUpdateCheckResult] = useState<UpdateCheckResult | null>(null);
|
|
551
|
+
const [startupUpdateDismissed, setStartupUpdateDismissed] = useState(false);
|
|
552
|
+
// Launcher path is fixed for the process lifetime, so detect once.
|
|
553
|
+
const globalPackageManager = useMemo(() => detectGlobalPackageManager(), []);
|
|
538
554
|
// Transcript mode leaves mouse reporting off so wheel/trackpad input scrolls
|
|
539
555
|
// the terminal emulator's native scrollback instead of an in-app viewport.
|
|
540
556
|
const mouseCapture = (mouseOverride ?? (terminalMouseMode === "wheel")) && !isMouseIdle;
|
|
@@ -658,6 +674,7 @@ export function App({ launchArgs }: AppProps) {
|
|
|
658
674
|
setLocalProviderConfig(providerWorkspaceConfig.providers?.local);
|
|
659
675
|
return buildProviderRegistry({
|
|
660
676
|
activeModel: model,
|
|
677
|
+
workspaceRoot,
|
|
661
678
|
workspaceConfig: providerWorkspaceConfig,
|
|
662
679
|
diagnostics: providerDiagnosticsRef.current,
|
|
663
680
|
routeErrors: providerRouteErrorsRef.current,
|
|
@@ -667,7 +684,7 @@ export function App({ launchArgs }: AppProps) {
|
|
|
667
684
|
// updates mutate providerDiagnosticsRef/providerRouteErrorsRef (refs, not state)
|
|
668
685
|
// and then increment the nonce to trigger a re-read of those refs.
|
|
669
686
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
670
|
-
[model, providerWorkspaceConfig, registryNonce],
|
|
687
|
+
[model, providerWorkspaceConfig, registryNonce, workspaceRoot],
|
|
671
688
|
);
|
|
672
689
|
const workspaceDefaultProvider = useMemo(
|
|
673
690
|
() => providerRegistry.find((provider) => provider.isDefault) ?? providerRegistry[0] ?? null,
|
|
@@ -717,7 +734,10 @@ export function App({ launchArgs }: AppProps) {
|
|
|
717
734
|
const modelPickerDiscovery = useMemo(() => {
|
|
718
735
|
if (modelPickerProviderId === "openai") return null;
|
|
719
736
|
return discoverProviderModels(modelPickerProviderId);
|
|
720
|
-
|
|
737
|
+
// registryNonce is intentionally included: route validation discovers models
|
|
738
|
+
// as a side effect and bumps the nonce so an open picker re-reads them.
|
|
739
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
740
|
+
}, [modelPickerProviderId, registryNonce]);
|
|
721
741
|
const providerModelCapabilities = useMemo(() => {
|
|
722
742
|
if (!modelPickerDiscovery) return null;
|
|
723
743
|
return providerModelsToCodexCapabilities(modelPickerDiscovery.models, activeProviderRoute.modelId);
|
|
@@ -1568,6 +1588,9 @@ export function App({ launchArgs }: AppProps) {
|
|
|
1568
1588
|
providerRouteErrorsRef.current["anthropic"] = result.message ?? ANTHROPIC_ROUTE_SETUP_MESSAGE;
|
|
1569
1589
|
} else {
|
|
1570
1590
|
delete providerRouteErrorsRef.current["anthropic"];
|
|
1591
|
+
// Persist the freshly discovered catalog so newly released Claude
|
|
1592
|
+
// models replace the stale on-disk cache without a manual refresh.
|
|
1593
|
+
persistProviderDiscovery(discoverProviderModels("anthropic"));
|
|
1571
1594
|
}
|
|
1572
1595
|
} catch {
|
|
1573
1596
|
// Best-effort probe — failures are surfaced only when the user activates the route.
|
|
@@ -1603,54 +1626,53 @@ export function App({ launchArgs }: AppProps) {
|
|
|
1603
1626
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1604
1627
|
}, [workspaceRoot]);
|
|
1605
1628
|
|
|
1606
|
-
// Non-blocking background update check —
|
|
1629
|
+
// Non-blocking background update check — fetches npm on every interactive startup.
|
|
1607
1630
|
useEffect(() => {
|
|
1608
1631
|
const ucSettings = initialSettings.current.updateCheck ?? DEFAULT_UPDATE_CHECK_SETTINGS;
|
|
1609
1632
|
if (!shouldRunStartupUpdateCheck(process.env, ucSettings.enabled)) return;
|
|
1610
1633
|
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
}
|
|
1642
|
-
}
|
|
1643
|
-
}
|
|
1644
|
-
} catch {
|
|
1645
|
-
// Never crash the TUI on a failed update check.
|
|
1646
|
-
}
|
|
1647
|
-
})();
|
|
1648
|
-
}, 2000);
|
|
1649
|
-
|
|
1650
|
-
return () => clearTimeout(timer);
|
|
1634
|
+
void (async () => {
|
|
1635
|
+
const cache = loadUpdateCheckCache();
|
|
1636
|
+
try {
|
|
1637
|
+
const result = await checkForUpdates({ enabled: true });
|
|
1638
|
+
if (result.status === "error") {
|
|
1639
|
+
// A previously confirmed update is still useful when npm is briefly
|
|
1640
|
+
// unreachable, but it must never suppress the next fresh startup check.
|
|
1641
|
+
if (cache?.updateAvailable && cache.latestVersion && isCacheForRunningVersion(cache, APP_VERSION)) {
|
|
1642
|
+
setUpdateCheckResult({
|
|
1643
|
+
status: "update-available",
|
|
1644
|
+
currentVersion: cache.currentVersion,
|
|
1645
|
+
latestVersion: cache.latestVersion,
|
|
1646
|
+
checkedAt: cache.lastChecked,
|
|
1647
|
+
source: "cache",
|
|
1648
|
+
});
|
|
1649
|
+
}
|
|
1650
|
+
return;
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
setUpdateCheckResult(result);
|
|
1654
|
+
saveUpdateCheckCache({
|
|
1655
|
+
lastChecked: result.checkedAt,
|
|
1656
|
+
currentVersion: result.currentVersion,
|
|
1657
|
+
latestVersion: result.latestVersion,
|
|
1658
|
+
updateAvailable: result.status === "update-available",
|
|
1659
|
+
});
|
|
1660
|
+
} catch {
|
|
1661
|
+
// Never crash the TUI on a failed update check.
|
|
1662
|
+
}
|
|
1663
|
+
})();
|
|
1651
1664
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1652
1665
|
}, []);
|
|
1653
1666
|
|
|
1667
|
+
// A startup update prompt must not interrupt an active run or another panel.
|
|
1668
|
+
// Keep the result until the user returns to the idle main screen instead.
|
|
1669
|
+
useEffect(() => {
|
|
1670
|
+
if (startupUpdateDismissed || busy || screen !== "main") return;
|
|
1671
|
+
if (updateCheckResult?.status === "update-available" && updateCheckResult.latestVersion) {
|
|
1672
|
+
setScreen("update-prompt");
|
|
1673
|
+
}
|
|
1674
|
+
}, [busy, screen, startupUpdateDismissed, updateCheckResult]);
|
|
1675
|
+
|
|
1654
1676
|
// Track clear epoch to suppress stale command result events
|
|
1655
1677
|
useEffect(() => {
|
|
1656
1678
|
clearEpochRef.current = sessionState.clearEpoch;
|
|
@@ -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
|
}
|
|
@@ -2126,32 +2160,10 @@ export function App({ launchArgs }: AppProps) {
|
|
|
2126
2160
|
}, [applyWorkspaceDisplayMode, showBusyLoader, terminalMouseMode, terminalTitleMode, workspaceDisplayMode]);
|
|
2127
2161
|
|
|
2128
2162
|
const handleSkipUpdateForSession = useCallback(() => {
|
|
2163
|
+
setStartupUpdateDismissed(true);
|
|
2129
2164
|
setScreen("main");
|
|
2130
2165
|
}, []);
|
|
2131
2166
|
|
|
2132
|
-
const handleSkipUpdateVersion = useCallback((version: string) => {
|
|
2133
|
-
initialSettings.current.updateCheck = {
|
|
2134
|
-
...initialSettings.current.updateCheck,
|
|
2135
|
-
skippedUpdateVersion: version,
|
|
2136
|
-
};
|
|
2137
|
-
saveSettings({
|
|
2138
|
-
ui: {
|
|
2139
|
-
layoutStyle: initialSettings.current.ui.layoutStyle,
|
|
2140
|
-
theme: themeSelection.committedTheme,
|
|
2141
|
-
workspaceDisplayMode,
|
|
2142
|
-
terminalTitleMode,
|
|
2143
|
-
showBusyLoader,
|
|
2144
|
-
terminalMouseMode,
|
|
2145
|
-
customTheme,
|
|
2146
|
-
},
|
|
2147
|
-
auth: { preference: authPreference },
|
|
2148
|
-
header: headerConfig,
|
|
2149
|
-
updateCheck: initialSettings.current.updateCheck,
|
|
2150
|
-
});
|
|
2151
|
-
skippedUpdateVersionRef.current = version;
|
|
2152
|
-
setScreen("main");
|
|
2153
|
-
}, [authPreference, customTheme, headerConfig, showBusyLoader, terminalMouseMode, terminalTitleMode, themeSelection.committedTheme, workspaceDisplayMode]);
|
|
2154
|
-
|
|
2155
2167
|
const setApprovalPolicyWithNotice = useCallback((nextValue: RuntimeApprovalPolicy) => {
|
|
2156
2168
|
const gate = guardConfigMutation("mode", busy);
|
|
2157
2169
|
if (!gate.allowed) {
|
|
@@ -2269,7 +2281,11 @@ export function App({ launchArgs }: AppProps) {
|
|
|
2269
2281
|
}, [appendSystemEvent, busy]);
|
|
2270
2282
|
|
|
2271
2283
|
const openProviderPicker = useCallback(() => {
|
|
2272
|
-
|
|
2284
|
+
// Mid route switch, keep the pending id so initialProviderId highlights
|
|
2285
|
+
// the provider being activated instead of the stale route.
|
|
2286
|
+
if (!modelSelectionInFlightRef.current) {
|
|
2287
|
+
setPendingRouteProviderId(null);
|
|
2288
|
+
}
|
|
2273
2289
|
const gate = guardConfigMutation("backend", busy);
|
|
2274
2290
|
if (!gate.allowed) {
|
|
2275
2291
|
appendSystemEvent("Busy", gate.message ?? "Finish the current run before opening providers.");
|
|
@@ -2277,6 +2293,31 @@ export function App({ launchArgs }: AppProps) {
|
|
|
2277
2293
|
}
|
|
2278
2294
|
|
|
2279
2295
|
setScreen("provider-picker");
|
|
2296
|
+
providerDiagnosticsRef.current.mistral = {
|
|
2297
|
+
...providerDiagnosticsRef.current.mistral,
|
|
2298
|
+
selectedModel: detectVibeActiveModel({ cwd: workspaceRoot }).modelId,
|
|
2299
|
+
availabilityStatus: "checking",
|
|
2300
|
+
};
|
|
2301
|
+
void resolveVibeExecutable({ cwd: workspaceRoot }).then((resolvedCommand) => {
|
|
2302
|
+
if (!isMountedRef.current) return;
|
|
2303
|
+
const modelDetection = detectVibeActiveModel({ cwd: workspaceRoot });
|
|
2304
|
+
providerDiagnosticsRef.current.mistral = {
|
|
2305
|
+
resolvedCommand,
|
|
2306
|
+
selectedModel: modelDetection.modelId,
|
|
2307
|
+
modelSource: modelDetection.source,
|
|
2308
|
+
configPath: modelDetection.configPath,
|
|
2309
|
+
availabilityStatus: resolvedCommand ? "available" : "unavailable",
|
|
2310
|
+
};
|
|
2311
|
+
setRegistryNonce((n) => n + 1);
|
|
2312
|
+
}).catch(() => {
|
|
2313
|
+
if (!isMountedRef.current) return;
|
|
2314
|
+
providerDiagnosticsRef.current.mistral = {
|
|
2315
|
+
selectedModel: detectVibeActiveModel({ cwd: workspaceRoot }).modelId,
|
|
2316
|
+
resolvedCommand: null,
|
|
2317
|
+
availabilityStatus: "unavailable",
|
|
2318
|
+
};
|
|
2319
|
+
setRegistryNonce((n) => n + 1);
|
|
2320
|
+
});
|
|
2280
2321
|
markProviderAvailability("local", "checking", "provider-picker-open");
|
|
2281
2322
|
void checkLocalProvider({ override: providerWorkspaceConfig.providers?.local }).then((result) => {
|
|
2282
2323
|
if (!isMountedRef.current) return;
|
|
@@ -2290,7 +2331,7 @@ export function App({ launchArgs }: AppProps) {
|
|
|
2290
2331
|
}
|
|
2291
2332
|
setRegistryNonce((n) => n + 1);
|
|
2292
2333
|
}).catch(() => undefined);
|
|
2293
|
-
}, [appendSystemEvent, busy, markProviderAvailability, providerWorkspaceConfig.providers]);
|
|
2334
|
+
}, [appendSystemEvent, busy, markProviderAvailability, providerWorkspaceConfig.providers, workspaceRoot]);
|
|
2294
2335
|
|
|
2295
2336
|
const setWorkspaceDefaultProviderWithNotice = useCallback((providerId: ProviderId) => {
|
|
2296
2337
|
const provider = findProvider(providerRegistry, providerId);
|
|
@@ -2307,7 +2348,9 @@ export function App({ launchArgs }: AppProps) {
|
|
|
2307
2348
|
const routeConfigured = isProviderRouteConfigured(providerId);
|
|
2308
2349
|
appendSystemEvent(
|
|
2309
2350
|
"Provider default updated",
|
|
2310
|
-
provider.routeMode === "
|
|
2351
|
+
provider.routeMode === "launch-only"
|
|
2352
|
+
? `${provider.displayName} is now the workspace default external CLI. Active chat route remains ${activeRouteProvider?.displayName ?? "OpenAI"} / ${model}.`
|
|
2353
|
+
: provider.routeMode === "in-codexa" && routeConfigured
|
|
2311
2354
|
? `${provider.displayName} is now the workspace default provider. Active chat route remains ${activeRouteProvider?.displayName ?? "OpenAI"} / ${model}.`
|
|
2312
2355
|
: `${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
2356
|
);
|
|
@@ -2466,6 +2509,7 @@ export function App({ launchArgs }: AppProps) {
|
|
|
2466
2509
|
cwd: workspaceRoot,
|
|
2467
2510
|
localConfig: providerId === "local" ? providerWorkspaceConfig.providers?.local : undefined,
|
|
2468
2511
|
}).then((discovery) => {
|
|
2512
|
+
persistProviderDiscovery(discovery);
|
|
2469
2513
|
if (discovery.diagnostics) {
|
|
2470
2514
|
providerDiagnosticsRef.current[providerId] = discovery.diagnostics as Record<string, string | number | boolean | null>;
|
|
2471
2515
|
}
|
|
@@ -2557,10 +2601,10 @@ export function App({ launchArgs }: AppProps) {
|
|
|
2557
2601
|
setScreen("main");
|
|
2558
2602
|
appendSystemEvent(
|
|
2559
2603
|
"Provider launch",
|
|
2560
|
-
`Suspending Codexa and launching ${provider.displayName}. Codexa will resume when the external CLI exits.`,
|
|
2604
|
+
`Suspending Codexa and launching ${provider.displayName}${providerId === "mistral" ? ` / ${provider.currentModel}` : ""}. Codexa will resume when the external CLI exits.`,
|
|
2561
2605
|
);
|
|
2562
2606
|
|
|
2563
|
-
|
|
2607
|
+
const launchOptions = {
|
|
2564
2608
|
cwd: workspaceRoot,
|
|
2565
2609
|
stdin,
|
|
2566
2610
|
beforeLaunch: () => {
|
|
@@ -2570,9 +2614,18 @@ export function App({ launchArgs }: AppProps) {
|
|
|
2570
2614
|
afterLaunch: () => {
|
|
2571
2615
|
terminalControl.setMouseReporting(effectiveMouseCapture, "src/app.tsx:providerLaunch.restoreMouse");
|
|
2572
2616
|
},
|
|
2573
|
-
}
|
|
2617
|
+
};
|
|
2618
|
+
const launchPromise = providerId === "mistral"
|
|
2619
|
+
? launchMistralVibeCli(provider, launchOptions)
|
|
2620
|
+
: launchProviderCli(provider, launchOptions);
|
|
2621
|
+
|
|
2622
|
+
void launchPromise.then((result) => {
|
|
2574
2623
|
if (!isMountedRef.current) return;
|
|
2575
|
-
|
|
2624
|
+
if (providerId === "mistral" && (result.status === "missing-command" || result.status === "spawn-error")) {
|
|
2625
|
+
appendErrorEvent("Mistral Vibe launch failed", result.message);
|
|
2626
|
+
} else {
|
|
2627
|
+
appendSystemEvent("Provider launch", result.message);
|
|
2628
|
+
}
|
|
2576
2629
|
}).catch((error) => {
|
|
2577
2630
|
if (!isMountedRef.current) return;
|
|
2578
2631
|
const message = error instanceof Error ? error.message : "Provider launch failed.";
|
|
@@ -2599,7 +2652,11 @@ export function App({ launchArgs }: AppProps) {
|
|
|
2599
2652
|
]);
|
|
2600
2653
|
|
|
2601
2654
|
const openModelPicker = useCallback(() => {
|
|
2602
|
-
|
|
2655
|
+
// While a route switch is validating, keep the pending id so the picker
|
|
2656
|
+
// shows the target provider instead of snapping back to the stale route.
|
|
2657
|
+
if (!modelSelectionInFlightRef.current) {
|
|
2658
|
+
setPendingRouteProviderId(null);
|
|
2659
|
+
}
|
|
2603
2660
|
traceInputDebug("model_picker_open_request", getInputDebugSnapshot({
|
|
2604
2661
|
handler: "openModelPicker",
|
|
2605
2662
|
currentScreen: screen,
|
|
@@ -3121,6 +3178,7 @@ export function App({ launchArgs }: AppProps) {
|
|
|
3121
3178
|
activeTurnIdRef.current = null;
|
|
3122
3179
|
activeRunLifecycleRef.current = null;
|
|
3123
3180
|
activeRunTimingRef.current = null;
|
|
3181
|
+
resetMistralVibeSession(workspaceRoot);
|
|
3124
3182
|
setPlanFlow(resetPlanFlow());
|
|
3125
3183
|
renderDebug.traceEvent("terminal", "clearReactStateRequested", {
|
|
3126
3184
|
clearGeneration,
|
|
@@ -3139,7 +3197,7 @@ export function App({ launchArgs }: AppProps) {
|
|
|
3139
3197
|
liveInkInstanceResolved: Boolean(inkInstance),
|
|
3140
3198
|
});
|
|
3141
3199
|
}
|
|
3142
|
-
}, [cancelActiveRun, clearFrameBoundaryController, inkInstance, launchContext, providerWorkspaceConfig, resetToHomeScreen, sessionState.clearEpoch, stdout.columns, terminalControl]);
|
|
3200
|
+
}, [cancelActiveRun, clearFrameBoundaryController, inkInstance, launchContext, providerWorkspaceConfig, resetToHomeScreen, sessionState.clearEpoch, stdout.columns, terminalControl, workspaceRoot]);
|
|
3143
3201
|
|
|
3144
3202
|
const handleShellExecute = useCallback((command: string) => {
|
|
3145
3203
|
const safeCommand = sanitizeTerminalInput(command).trim();
|
|
@@ -4174,13 +4232,13 @@ export function App({ launchArgs }: AppProps) {
|
|
|
4174
4232
|
case "diagnose_providers": {
|
|
4175
4233
|
const lines: string[] = ["Provider CLI diagnostics:"];
|
|
4176
4234
|
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
|
-
};
|
|
4235
|
+
const providerIds = ["openai", "anthropic", "local", "antigravity"] as const;
|
|
4236
|
+
const labels: Record<string, string> = {
|
|
4237
|
+
openai: "OpenAI/Codex",
|
|
4238
|
+
anthropic: "Anthropic/Claude",
|
|
4239
|
+
local: "Local OpenAI-compatible",
|
|
4240
|
+
antigravity: "Antigravity CLI",
|
|
4241
|
+
};
|
|
4184
4242
|
for (const id of providerIds) {
|
|
4185
4243
|
const diag = diags[id];
|
|
4186
4244
|
lines.push(`\n ${labels[id] ?? id}:`);
|
|
@@ -4355,7 +4413,10 @@ export function App({ launchArgs }: AppProps) {
|
|
|
4355
4413
|
if (freshResult?.status === "update-available" && freshResult.latestVersion) {
|
|
4356
4414
|
setScreen("update-prompt");
|
|
4357
4415
|
} else {
|
|
4358
|
-
appendSystemEvent(
|
|
4416
|
+
appendSystemEvent(
|
|
4417
|
+
"Update",
|
|
4418
|
+
formatUpdateInstructions(freshResult, getUpdateCommand(globalPackageManager).displayCommand),
|
|
4419
|
+
);
|
|
4359
4420
|
}
|
|
4360
4421
|
})();
|
|
4361
4422
|
return;
|
|
@@ -4447,6 +4508,7 @@ export function App({ launchArgs }: AppProps) {
|
|
|
4447
4508
|
dispatchSession,
|
|
4448
4509
|
findUserPromptForTurn,
|
|
4449
4510
|
focusManager,
|
|
4511
|
+
globalPackageManager,
|
|
4450
4512
|
handleCopy,
|
|
4451
4513
|
handleClear,
|
|
4452
4514
|
handleQuit,
|
|
@@ -4671,8 +4733,12 @@ export function App({ launchArgs }: AppProps) {
|
|
|
4671
4733
|
clearCount={sessionState.clearCount}
|
|
4672
4734
|
headerConfig={effectiveHeaderConfig}
|
|
4673
4735
|
updateAvailable={
|
|
4674
|
-
updateCheckResult?.status === "update-available" && updateCheckResult.latestVersion
|
|
4675
|
-
? {
|
|
4736
|
+
screen !== "update-prompt" && updateCheckResult?.status === "update-available" && updateCheckResult.latestVersion
|
|
4737
|
+
? {
|
|
4738
|
+
latestVersion: updateCheckResult.latestVersion,
|
|
4739
|
+
currentVersion: updateCheckResult.currentVersion,
|
|
4740
|
+
updateCommand: getUpdateCommand(globalPackageManager).displayCommand,
|
|
4741
|
+
}
|
|
4676
4742
|
: null
|
|
4677
4743
|
}
|
|
4678
4744
|
panel={
|
|
@@ -4705,7 +4771,8 @@ export function App({ launchArgs }: AppProps) {
|
|
|
4705
4771
|
currentModel={modelPickerCurrentModel}
|
|
4706
4772
|
currentReasoning={modelPickerCurrentReasoning}
|
|
4707
4773
|
activeProviderLabel={modelPickerProviderLabel}
|
|
4708
|
-
isLoading={
|
|
4774
|
+
isLoading={modelPickerModels.length === 0
|
|
4775
|
+
&& ((modelPickerProviderId === "openai" && modelCapabilitiesBusy) || routeSwitchBusy)}
|
|
4709
4776
|
emptyMessage={modelPickerEmptyMessage}
|
|
4710
4777
|
onSelect={(m, r, geminiSelection) => {
|
|
4711
4778
|
if (pendingRouteProviderId && pendingRouteProviderId !== activeProviderRoute.providerId) {
|
|
@@ -4929,9 +4996,9 @@ export function App({ launchArgs }: AppProps) {
|
|
|
4929
4996
|
focusId={FOCUS_IDS.updatePrompt}
|
|
4930
4997
|
currentVersion={updateCheckResult.currentVersion}
|
|
4931
4998
|
latestVersion={updateCheckResult.latestVersion}
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
/>
|
|
4999
|
+
packageManager={globalPackageManager}
|
|
5000
|
+
onSkip={handleSkipUpdateForSession}
|
|
5001
|
+
/>
|
|
4935
5002
|
)}
|
|
4936
5003
|
</>
|
|
4937
5004
|
}
|