@golba98/codexa 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.md +17 -18
  2. package/bin/codexa.js +62 -144
  3. package/package.json +4 -3
  4. package/src/app.tsx +533 -275
  5. package/src/commands/handler.ts +2 -2
  6. package/src/config/buildInfo.ts +2 -2
  7. package/src/config/layeredConfig.ts +1 -1
  8. package/src/config/runtimeConfig.ts +1 -1
  9. package/src/config/settings.ts +1 -1
  10. package/src/config/trustStore.ts +1 -1
  11. package/src/core/README.md +52 -0
  12. package/src/core/agent/loop.ts +282 -0
  13. package/src/core/agent/protocol.ts +211 -0
  14. package/src/core/agent/tools.ts +414 -0
  15. package/src/core/{codexExecArgs.ts → codex/codexExecArgs.ts} +2 -2
  16. package/src/core/{codexLaunch.ts → codex/codexLaunch.ts} +3 -3
  17. package/src/core/{codexPrompt.ts → codex/codexPrompt.ts} +3 -3
  18. package/src/core/debug/modelStateDebug.ts +34 -0
  19. package/src/core/executables/antigravityExecutable.ts +48 -0
  20. package/src/core/executables/executableResolver.ts +5 -1
  21. package/src/core/perf/renderDebug.ts +10 -6
  22. package/src/core/providerLauncher/registry.ts +30 -14
  23. package/src/core/providerLauncher/types.ts +11 -9
  24. package/src/core/providerLauncher/workspaceConfig.ts +41 -26
  25. package/src/core/providerRuntime/anthropic.ts +7 -1
  26. package/src/core/providerRuntime/antigravity.ts +305 -0
  27. package/src/core/providerRuntime/claudeCodeDiscovery.ts +268 -22
  28. package/src/core/providerRuntime/claudeCodeDiscoveryDebug.ts +55 -0
  29. package/src/core/providerRuntime/contextMetadata.ts +12 -24
  30. package/src/core/providerRuntime/local.ts +129 -51
  31. package/src/core/providerRuntime/models.ts +22 -11
  32. package/src/core/providerRuntime/registry.ts +58 -31
  33. package/src/core/providerRuntime/types.ts +19 -14
  34. package/src/core/providers/codexSubprocess.ts +2 -2
  35. package/src/core/providers/types.ts +1 -1
  36. package/src/core/{attachments.ts → shared/attachments.ts} +27 -4
  37. package/src/core/{cleanupFastFail.ts → shared/cleanupFastFail.ts} +1 -1
  38. package/src/core/{hollowResponseFormat.ts → shared/hollowResponseFormat.ts} +1 -1
  39. package/src/core/terminal/clearFrameBoundary.ts +814 -0
  40. package/src/core/terminal/frameLock.ts +109 -0
  41. package/src/core/terminal/inkRenderReset.ts +123 -0
  42. package/src/core/terminal/terminalControl.ts +22 -0
  43. package/src/core/terminal/terminalTitle.ts +16 -102
  44. package/src/core/{channel.ts → version/channel.ts} +1 -1
  45. package/src/core/{updateCheck.ts → version/updateCheck.ts} +10 -5
  46. package/src/core/{launchContext.ts → workspace/launchContext.ts} +9 -16
  47. package/src/core/{planStorage.ts → workspace/planStorage.ts} +2 -2
  48. package/src/core/{workspaceGuard.ts → workspace/workspaceGuard.ts} +97 -13
  49. package/src/headless/execRunner.ts +2 -2
  50. package/src/index.tsx +43 -89
  51. package/src/session/appSession.ts +10 -7
  52. package/src/session/chatLifecycle.ts +1 -1
  53. package/src/session/liveRenderScheduler.ts +1 -1
  54. package/src/session/types.ts +1 -1
  55. package/src/ui/AppShell.tsx +672 -706
  56. package/src/ui/AttachmentImportPanel.tsx +2 -2
  57. package/src/ui/BottomComposer.tsx +145 -144
  58. package/src/ui/ModelPickerScreen.tsx +216 -36
  59. package/src/ui/ModelReasoningPicker.tsx +1 -1
  60. package/src/ui/PlanReviewPanel.tsx +1 -1
  61. package/src/ui/ProviderPicker.tsx +735 -321
  62. package/src/ui/RuntimeStatusBar.tsx +108 -0
  63. package/src/ui/SelectionPanel.tsx +4 -0
  64. package/src/ui/SettingsPanel.tsx +1 -1
  65. package/src/ui/TextEntryPanel.tsx +1 -1
  66. package/src/ui/Timeline.tsx +1619 -1470
  67. package/src/ui/TopHeader.tsx +46 -30
  68. package/src/ui/TranscriptShell.tsx +322 -0
  69. package/src/ui/TurnGroup.tsx +2 -2
  70. package/src/ui/UpdateAvailableCard.tsx +3 -3
  71. package/src/ui/UpdatePromptPanel.tsx +16 -22
  72. package/src/ui/layout.ts +298 -24
  73. package/src/ui/layoutListWindow.ts +145 -0
  74. package/src/ui/logoVariants.ts +4 -8
  75. package/src/ui/runtimeDisplay.ts +15 -3
  76. package/src/ui/textLayout.ts +15 -4
  77. package/src/ui/timelineMeasure.ts +194 -122
  78. package/src/core/codex.ts +0 -124
  79. package/src/ui/StaticTranscriptItem.tsx +0 -56
  80. /package/src/core/{inputDebug.ts → debug/inputDebug.ts} +0 -0
  81. /package/src/core/{clipboard.ts → shared/clipboard.ts} +0 -0
  82. /package/src/core/{githubDiagnostics.ts → shared/githubDiagnostics.ts} +0 -0
  83. /package/src/core/{projectInstructions.ts → workspace/projectInstructions.ts} +0 -0
  84. /package/src/core/{workspaceActivity.ts → workspace/workspaceActivity.ts} +0 -0
  85. /package/src/core/{workspaceRoot.ts → workspace/workspaceRoot.ts} +0 -0
@@ -6,18 +6,36 @@ import {
6
6
  normalizeReasoningForModelCapabilities,
7
7
  } from "../core/models/codexModelCapabilities.js";
8
8
  import { formatReasoningLabel } from "../config/settings.js";
9
- import { traceInputDebug } from "../core/inputDebug.js";
9
+ import { traceInputDebug } from "../core/debug/inputDebug.js";
10
10
  import { FOCUS_IDS } from "./focus.js";
11
- import { clampVisualText, getShellWidth, type Layout } from "./layout.js";
11
+ import {
12
+ clampVisualText,
13
+ getShellWidth,
14
+ getAvailableRowsForPanel,
15
+ useAppLayoutBudget,
16
+ usePanelAvailableRows,
17
+ type Layout,
18
+ useActivePanelLayout,
19
+ type ActivePanelLayout,
20
+ type PanelLayout,
21
+ usePanelLayout,
22
+ } from "./layout.js";
23
+ import { calculateListWindow } from "./layoutListWindow.js";
12
24
  import { useTheme } from "./theme.js";
13
25
  import type { GeminiModelSelection } from "../core/providerRuntime/types.js";
14
26
 
15
27
  // ─── Types & helpers ─────────────────────────────────────────────────────────
16
28
 
17
29
  type ModelPickerCloseReason = "escape" | "empty-selection";
30
+ type ModelRenderMode = "full" | "compact" | "windowed";
18
31
 
19
32
  interface ModelPickerScreenProps {
20
- layout: Layout;
33
+ layout: Layout & {
34
+ contentWidth?: number;
35
+ };
36
+ availableRows?: number;
37
+ activePanelLayout?: ActivePanelLayout;
38
+ panelLayout?: PanelLayout;
21
39
  models: readonly CodexModelCapability[];
22
40
  currentModel: string;
23
41
  currentReasoning: string;
@@ -50,10 +68,25 @@ function getReasoningLevels(model: CodexModelCapability | undefined): readonly R
50
68
 
51
69
  function getModelSourceMarker(models: readonly CodexModelCapability[], activeProviderLabel: string): string | null {
52
70
  if (activeProviderLabel !== "Claude" || models.length === 0) return null;
53
- const source = (models[0]?.raw as { source?: string } | null | undefined)?.source;
54
- if (source === "claude-code" || source === "discovered") return "Discovered from Claude Code";
55
- if (source === "settings" || source === "config") return "From Claude settings";
56
- return "Fallback defaults";
71
+ const raw = models[0]?.raw as { source?: string; discoveryKind?: string } | null | undefined;
72
+ const source = raw?.source;
73
+ const sourceLabel = source === "claude-code-package"
74
+ ? "installed package metadata"
75
+ : source === "claude-code-command"
76
+ ? "Claude Code command"
77
+ : source === "claude-code-cache"
78
+ ? "Claude Code cache"
79
+ : source === "claude-code-config" || source === "settings" || source === "config"
80
+ ? "Claude settings"
81
+ : "Claude Code";
82
+ if (source === "claude-code-package" || source === "claude-code-command" || source === "claude-code-cache" || source === "claude-code-config" || source === "claude-code" || source === "discovered") {
83
+ return raw?.discoveryKind === "aliases"
84
+ ? `Claude Code aliases resolved from ${sourceLabel}`
85
+ : `Claude Code models discovered from ${sourceLabel}`;
86
+ }
87
+ if (source === "settings" || source === "config") return "Claude Code models discovered from Claude settings";
88
+ if (source === "fallback") return "Claude Code discovery failed; using fallback aliases";
89
+ return null;
57
90
  }
58
91
 
59
92
  function normalizeDraftReasoning(
@@ -109,6 +142,9 @@ function describeInputKey(
109
142
 
110
143
  export function ModelPickerScreen({
111
144
  layout,
145
+ availableRows: propAvailableRows,
146
+ activePanelLayout,
147
+ panelLayout,
112
148
  models: baseModels,
113
149
  currentModel,
114
150
  currentReasoning,
@@ -279,13 +315,40 @@ export function ModelPickerScreen({
279
315
  { isActive: isFocused },
280
316
  );
281
317
 
318
+ const contextLayout = useActivePanelLayout();
319
+ const activeLayout = (activePanelLayout ?? contextLayout) as ActivePanelLayout | undefined;
320
+
282
321
  const shellWidth = getShellWidth(layout.cols);
283
- const panelWidth = Math.max(38, Math.min(shellWidth - 2, layout.mode === "full" ? 74 : 64));
284
- const innerWidth = Math.max(20, panelWidth - 4);
285
- const help = layout.mode === "micro"
322
+ const hookPanelLayout = usePanelLayout();
323
+ const hookAvailableRows = usePanelAvailableRows();
324
+ const resolvedPanelLayout = useMemo<PanelLayout>(() => {
325
+ if (panelLayout) return panelLayout;
326
+ if (hookPanelLayout) return hookPanelLayout;
327
+
328
+ const mode = layout.mode;
329
+ const resolvedRows = activeLayout
330
+ ? activeLayout.availableRows
331
+ : getAvailableRowsForPanel(layout, propAvailableRows ?? hookAvailableRows);
332
+ const resolvedCols = activeLayout
333
+ ? activeLayout.availableCols
334
+ : Math.max(20, shellWidth - 4);
335
+
336
+ return {
337
+ mode: (mode === "compact" || mode === "micro" as any) ? "compact" : mode === "expanded" || mode === "max" as any || mode === "wide" as any ? "expanded" : "regular",
338
+ availableRows: resolvedRows,
339
+ availableCols: resolvedCols,
340
+ };
341
+ }, [panelLayout, hookPanelLayout, layout, activeLayout, propAvailableRows, shellWidth, hookAvailableRows]);
342
+
343
+ const panelWidth = activeLayout
344
+ ? activeLayout.width
345
+ : Math.max(38, Math.min((layout as any).contentWidth ?? shellWidth, shellWidth - 2));
346
+
347
+ const availableRows = resolvedPanelLayout.availableRows;
348
+ const innerWidth = resolvedPanelLayout.availableCols;
349
+ const help = resolvedPanelLayout.mode === "compact"
286
350
  ? "↑↓ · ←→ · Enter · Esc"
287
351
  : "↑↓ model · ←→ reasoning · Enter select · Esc cancel";
288
- const title = clampVisualText(`Select model ${help}`, innerWidth);
289
352
  const aOrAn = /^[aeiou]/i.test(activeProviderLabel) ? "an" : "a";
290
353
  const routeText = routeTextOverride ?? `Choose ${aOrAn} ${activeProviderLabel} model to use inside Codexa.`;
291
354
  const reasoningText = reasoningUnavailable
@@ -293,6 +356,92 @@ export function ModelPickerScreen({
293
356
  : `Reasoning: ${formatReasoningLabel(draftReasoning)}`;
294
357
  const sourceMarker = getModelSourceMarker(models, activeProviderLabel);
295
358
 
359
+ const appLayoutBudget = useAppLayoutBudget();
360
+
361
+ const activeModelIndex = models.findIndex((m) => m.model === currentModel || m.id === currentModel);
362
+ const hasSourceMarker = !!sourceMarker;
363
+
364
+ // ─── Layout & Windowing ───────────────────────────────────────────────────
365
+
366
+ const windowResult = useMemo(() => {
367
+ const allowFull = appLayoutBudget?.showPanelColumnHeaders ?? true;
368
+
369
+ // Try fitting with full metadata
370
+ const fullChrome = 5 + (hasSourceMarker ? 1 : 0);
371
+ if (allowFull && models.length + fullChrome <= availableRows) {
372
+ return {
373
+ ...calculateListWindow({ itemCount: models.length, selectedIndex: draftSelectedModel, availableRows, chromeRows: fullChrome, showIndicators: false }),
374
+ mode: "full" as const,
375
+ showRouteText: true,
376
+ showReasoningText: true,
377
+ showSourceMarker: hasSourceMarker,
378
+ };
379
+ }
380
+
381
+ // Try fitting without source marker
382
+ if (allowFull && models.length + 5 <= availableRows) {
383
+ return {
384
+ ...calculateListWindow({ itemCount: models.length, selectedIndex: draftSelectedModel, availableRows, chromeRows: 5, showIndicators: false }),
385
+ mode: "full" as const,
386
+ showRouteText: true,
387
+ showReasoningText: true,
388
+ showSourceMarker: false,
389
+ };
390
+ }
391
+
392
+ // Try compact with reasoning
393
+ if (models.length + 4 <= availableRows) {
394
+ return {
395
+ ...calculateListWindow({ itemCount: models.length, selectedIndex: draftSelectedModel, availableRows, chromeRows: 4, showIndicators: false }),
396
+ mode: "compact" as const,
397
+ showRouteText: false,
398
+ showReasoningText: true,
399
+ showSourceMarker: false,
400
+ };
401
+ }
402
+
403
+ // Try minimal compact
404
+ if (models.length + 3 <= availableRows) {
405
+ return {
406
+ ...calculateListWindow({ itemCount: models.length, selectedIndex: draftSelectedModel, availableRows, chromeRows: 3, showIndicators: false }),
407
+ mode: "compact" as const,
408
+ showRouteText: false,
409
+ showReasoningText: false,
410
+ showSourceMarker: false,
411
+ };
412
+ }
413
+
414
+ // Windowed mode
415
+ const window = calculateListWindow({
416
+ itemCount: models.length,
417
+ selectedIndex: draftSelectedModel,
418
+ availableRows,
419
+ chromeRows: 3, // Title (1) + Border (2)
420
+ showIndicators: true,
421
+ });
422
+
423
+ return {
424
+ ...window,
425
+ mode: "windowed" as const,
426
+ showRouteText: false,
427
+ showReasoningText: false,
428
+ showSourceMarker: false,
429
+ };
430
+ }, [models.length, draftSelectedModel, availableRows, hasSourceMarker, appLayoutBudget?.showPanelColumnHeaders]);
431
+
432
+ const visibleModels = useMemo(() => {
433
+ return models.slice(windowResult.start, windowResult.end);
434
+ }, [models, windowResult.start, windowResult.end]);
435
+
436
+ const activeModel = models[activeModelIndex];
437
+ const showCurrentLine = windowResult.mode === "windowed" && activeModelIndex >= 0 && (activeModelIndex < windowResult.start || activeModelIndex >= windowResult.end);
438
+ const title = clampVisualText(
439
+ windowResult.mode === "windowed"
440
+ ? `Select model · Showing ${windowResult.start + 1}-${windowResult.end} of ${models.length}`
441
+ : `Select model ${help}`,
442
+ innerWidth,
443
+ );
444
+
296
445
  return (
297
446
  <Box flexDirection="column" width={panelWidth}>
298
447
  <Box
@@ -306,17 +455,21 @@ export function ModelPickerScreen({
306
455
  <Box width="100%" overflow="hidden">
307
456
  <Text color={theme.accent} bold>{title}</Text>
308
457
  </Box>
309
- <Box width="100%" overflow="hidden">
310
- <Text color={theme.textMuted}>
311
- {clampVisualText(routeText, innerWidth)}
312
- </Text>
313
- </Box>
314
- <Box width="100%" overflow="hidden">
315
- <Text color={reasoningUnavailable ? theme.textDim : theme.textMuted}>
316
- {clampVisualText(reasoningText, innerWidth)}
317
- </Text>
318
- </Box>
319
- {sourceMarker && (
458
+ {windowResult.showRouteText && (
459
+ <Box width="100%" overflow="hidden">
460
+ <Text color={theme.textMuted}>
461
+ {clampVisualText(routeText, innerWidth)}
462
+ </Text>
463
+ </Box>
464
+ )}
465
+ {windowResult.showReasoningText && (
466
+ <Box width="100%" overflow="hidden">
467
+ <Text color={reasoningUnavailable ? theme.textDim : theme.textMuted}>
468
+ {clampVisualText(reasoningText, innerWidth)}
469
+ </Text>
470
+ </Box>
471
+ )}
472
+ {windowResult.showSourceMarker && sourceMarker && (
320
473
  <Box width="100%" overflow="hidden">
321
474
  <Text color={theme.textDim}>
322
475
  {clampVisualText(sourceMarker, innerWidth)}
@@ -324,25 +477,52 @@ export function ModelPickerScreen({
324
477
  </Box>
325
478
  )}
326
479
 
327
- <Box flexDirection="column" marginTop={0} width="100%">
480
+ {showCurrentLine && activeModel && (
481
+ <Box height={1} overflow="hidden">
482
+ <Text color={theme.textMuted} wrap="truncate">Current: <Text color={theme.text} bold>{getModelName(activeModel)}</Text></Text>
483
+ </Box>
484
+ )}
485
+
486
+ {windowResult.showAbove && (
487
+ <Box height={1} overflow="hidden">
488
+ <Text color={theme.accent}>↑ {windowResult.start} more</Text>
489
+ </Box>
490
+ )}
491
+
492
+ <Box
493
+ flexDirection="column"
494
+ marginTop={0}
495
+ width="100%"
496
+ height={models.length > 0 ? visibleModels.length : undefined}
497
+ overflow={models.length > 0 ? "hidden" : undefined}
498
+ >
328
499
  {models.length === 0 ? (
329
500
  <Text color={theme.textMuted}>
330
501
  {isLoading ? "Discovering models from the Codex runtime..." : (emptyMessage ?? "No models available.")}
331
502
  </Text>
332
503
  ) : (
333
- models.map((model, index) => (
334
- <ModelPickerRow
335
- key={model.id}
336
- model={model}
337
- width={innerWidth}
338
- currentModel={currentModel}
339
- currentGeminiSelection={currentGeminiSelection}
340
- isHighlighted={index === draftSelectedModel}
341
- selectedReasoning={index === draftSelectedModel ? draftReasoning : normalizeDraftReasoning(model, currentReasoning)}
342
- />
343
- ))
504
+ visibleModels.map((model, index) => {
505
+ const actualIndex = windowResult.start + index;
506
+ return (
507
+ <ModelPickerRow
508
+ key={model.id}
509
+ model={model}
510
+ width={innerWidth}
511
+ currentModel={currentModel}
512
+ currentGeminiSelection={currentGeminiSelection}
513
+ isHighlighted={actualIndex === draftSelectedModel}
514
+ selectedReasoning={actualIndex === draftSelectedModel ? draftReasoning : normalizeDraftReasoning(model, currentReasoning)}
515
+ />
516
+ );
517
+ })
344
518
  )}
345
519
  </Box>
520
+
521
+ {windowResult.showBelow && (
522
+ <Box height={1} overflow="hidden">
523
+ <Text color={theme.accent}>↓ {windowResult.hiddenBelow} more</Text>
524
+ </Box>
525
+ )}
346
526
  </Box>
347
527
  </Box>
348
528
  );
@@ -392,7 +572,7 @@ function ModelPickerRow({
392
572
  <Text color={isHighlighted ? theme.accent : theme.textDim}>{isHighlighted ? ">" : " "}</Text>
393
573
  </Box>
394
574
  <Box width={nameWidth} flexShrink={0} overflow="hidden">
395
- <Text color={isHighlighted ? theme.text : theme.textMuted} bold={isHighlighted}>
575
+ <Text color={isHighlighted ? theme.text : theme.textMuted} bold={isHighlighted} wrap="truncate">
396
576
  {name}
397
577
  </Text>
398
578
  </Box>
@@ -405,7 +585,7 @@ function ModelPickerRow({
405
585
  <Text> </Text>
406
586
  </Box>
407
587
  <Box width={pillWidth} flexShrink={0} overflow="hidden">
408
- <Text color={theme.accent} bold>
588
+ <Text color={theme.accent} bold wrap="truncate">
409
589
  {pillText}
410
590
  </Text>
411
591
  </Box>
@@ -6,7 +6,7 @@ import {
6
6
  normalizeReasoningForModelCapabilities,
7
7
  } from "../core/models/codexModelCapabilities.js";
8
8
  import { formatReasoningLabel } from "../config/settings.js";
9
- import { traceInputDebug } from "../core/inputDebug.js";
9
+ import { traceInputDebug } from "../core/debug/inputDebug.js";
10
10
  import { FOCUS_IDS } from "./focus.js";
11
11
  import { useTheme } from "./theme.js";
12
12
 
@@ -1,6 +1,6 @@
1
1
  import { useMemo } from "react";
2
2
  import { Box, Text } from "ink";
3
- import { normalizePlanReviewMarkdown } from "../core/planStorage.js";
3
+ import { normalizePlanReviewMarkdown } from "../core/workspace/planStorage.js";
4
4
  import { getUsableShellWidth } from "./layout.js";
5
5
  import { parseMarkdown } from "./Markdown.js";
6
6
  import { getTextWidth } from "./textLayout.js";