@golba98/codexa 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. package/README.md +396 -100
  2. package/bin/codexa.js +62 -144
  3. package/package.json +14 -8
  4. package/src/app.tsx +596 -306
  5. package/src/commands/handler.ts +6 -6
  6. package/src/config/buildInfo.ts +2 -2
  7. package/src/config/layeredConfig.ts +1 -1
  8. package/src/config/persistence.ts +10 -0
  9. package/src/config/runtimeConfig.ts +1 -1
  10. package/src/config/settings.ts +8 -16
  11. package/src/config/trustStore.ts +1 -1
  12. package/src/config/updateCheckCache.ts +19 -1
  13. package/src/core/README.md +52 -0
  14. package/src/core/agent/loop.ts +282 -0
  15. package/src/core/agent/protocol.ts +211 -0
  16. package/src/core/agent/tools.ts +414 -0
  17. package/src/core/{codexExecArgs.ts → codex/codexExecArgs.ts} +2 -2
  18. package/src/core/{codexLaunch.ts → codex/codexLaunch.ts} +3 -3
  19. package/src/core/{codexPrompt.ts → codex/codexPrompt.ts} +3 -3
  20. package/src/core/debug/modelStateDebug.ts +34 -0
  21. package/src/core/executables/antigravityExecutable.ts +48 -0
  22. package/src/core/executables/codexExecutable.ts +1 -0
  23. package/src/core/executables/executableResolver.ts +65 -43
  24. package/src/core/perf/renderDebug.ts +10 -6
  25. package/src/core/process/processValidation.ts +9 -5
  26. package/src/core/providerLauncher/launcher.ts +59 -42
  27. package/src/core/providerLauncher/registry.ts +30 -14
  28. package/src/core/providerLauncher/types.ts +11 -9
  29. package/src/core/providerLauncher/workspaceConfig.ts +41 -26
  30. package/src/core/providerRuntime/anthropic.ts +7 -1
  31. package/src/core/providerRuntime/antigravity.ts +305 -0
  32. package/src/core/providerRuntime/claudeCodeDiscovery.ts +268 -22
  33. package/src/core/providerRuntime/claudeCodeDiscoveryDebug.ts +55 -0
  34. package/src/core/providerRuntime/contextMetadata.ts +12 -24
  35. package/src/core/providerRuntime/local.ts +129 -51
  36. package/src/core/providerRuntime/models.ts +22 -11
  37. package/src/core/providerRuntime/registry.ts +58 -31
  38. package/src/core/providerRuntime/types.ts +19 -14
  39. package/src/core/providers/codexSubprocess.ts +2 -2
  40. package/src/core/providers/types.ts +1 -1
  41. package/src/core/{attachments.ts → shared/attachments.ts} +27 -4
  42. package/src/core/{cleanupFastFail.ts → shared/cleanupFastFail.ts} +1 -1
  43. package/src/core/{hollowResponseFormat.ts → shared/hollowResponseFormat.ts} +1 -1
  44. package/src/core/terminal/clearFrameBoundary.ts +814 -0
  45. package/src/core/terminal/frameLock.ts +109 -0
  46. package/src/core/terminal/inkRenderReset.ts +123 -0
  47. package/src/core/terminal/terminalControl.ts +22 -0
  48. package/src/core/terminal/terminalTitle.ts +16 -102
  49. package/src/core/version/channel.ts +23 -0
  50. package/src/core/version/updateCheck.ts +193 -0
  51. package/src/core/{launchContext.ts → workspace/launchContext.ts} +32 -39
  52. package/src/core/{planStorage.ts → workspace/planStorage.ts} +2 -2
  53. package/src/core/{workspaceGuard.ts → workspace/workspaceGuard.ts} +97 -13
  54. package/src/headless/execRunner.ts +2 -2
  55. package/src/index.tsx +43 -89
  56. package/src/session/appSession.ts +10 -7
  57. package/src/session/chatLifecycle.ts +1 -1
  58. package/src/session/liveRenderScheduler.ts +1 -1
  59. package/src/session/types.ts +3 -2
  60. package/src/ui/ActionRequiredBlock.tsx +5 -5
  61. package/src/ui/ActivityBars.tsx +3 -3
  62. package/src/ui/ActivityIndicator.tsx +6 -6
  63. package/src/ui/AgentBlock.tsx +6 -6
  64. package/src/ui/AnimatedStatusText.tsx +1 -1
  65. package/src/ui/AppShell.tsx +670 -719
  66. package/src/ui/AttachmentImportPanel.tsx +8 -8
  67. package/src/ui/AuthPanel.tsx +20 -20
  68. package/src/ui/BottomComposer.tsx +158 -118
  69. package/src/ui/DashCard.tsx +3 -3
  70. package/src/ui/Markdown.tsx +17 -17
  71. package/src/ui/ModelPickerScreen.tsx +222 -42
  72. package/src/ui/ModelReasoningPicker.tsx +15 -15
  73. package/src/ui/Panel.tsx +3 -3
  74. package/src/ui/PlanActionPicker.tsx +6 -6
  75. package/src/ui/PlanReviewPanel.tsx +9 -9
  76. package/src/ui/ProviderPicker.tsx +735 -321
  77. package/src/ui/RunFooter.tsx +3 -3
  78. package/src/ui/RuntimeStatusBar.tsx +108 -0
  79. package/src/ui/SelectionPanel.tsx +8 -4
  80. package/src/ui/SettingsPanel.tsx +9 -9
  81. package/src/ui/Spinner.tsx +1 -1
  82. package/src/ui/TextEntryPanel.tsx +11 -11
  83. package/src/ui/ThinkingBlock.tsx +8 -8
  84. package/src/ui/Timeline.tsx +1625 -1472
  85. package/src/ui/TopHeader.tsx +437 -293
  86. package/src/ui/TranscriptShell.tsx +322 -0
  87. package/src/ui/TurnGroup.tsx +33 -33
  88. package/src/ui/UpdateAvailableCard.tsx +41 -0
  89. package/src/ui/UpdatePromptPanel.tsx +197 -0
  90. package/src/ui/focus.ts +3 -0
  91. package/src/ui/layout.ts +299 -25
  92. package/src/ui/layoutListWindow.ts +145 -0
  93. package/src/ui/logoVariants.ts +103 -0
  94. package/src/ui/modeDisplay.ts +12 -12
  95. package/src/ui/runtimeDisplay.ts +112 -0
  96. package/src/ui/textLayout.ts +15 -4
  97. package/src/ui/theme.tsx +274 -395
  98. package/src/ui/timelineMeasure.ts +218 -136
  99. package/scripts/audit-codexa-capabilities.mjs +0 -466
  100. package/scripts/gen-build-info.mjs +0 -33
  101. package/scripts/smoke-terminal-bench.mjs +0 -35
  102. package/src/appRenderStability.test.ts +0 -131
  103. package/src/commands/handler.test.ts +0 -655
  104. package/src/config/launchArgs.test.ts +0 -189
  105. package/src/config/layeredConfig.test.ts +0 -143
  106. package/src/config/persistence.test.ts +0 -114
  107. package/src/config/runtimeConfig.test.ts +0 -218
  108. package/src/config/settings.test.ts +0 -155
  109. package/src/config/trustStore.test.ts +0 -29
  110. package/src/core/attachments.test.ts +0 -155
  111. package/src/core/auth/codexAuth.test.ts +0 -68
  112. package/src/core/cleanupFastFail.test.ts +0 -76
  113. package/src/core/codex.ts +0 -124
  114. package/src/core/codexExecArgs.test.ts +0 -195
  115. package/src/core/codexLaunch.test.ts +0 -205
  116. package/src/core/codexPrompt.test.ts +0 -252
  117. package/src/core/executables/codexExecutable.test.ts +0 -212
  118. package/src/core/executables/executableResolver.test.ts +0 -129
  119. package/src/core/executables/geminiExecutable.test.ts +0 -116
  120. package/src/core/executables/pathSanityScan.test.ts +0 -47
  121. package/src/core/githubDiagnostics.test.ts +0 -92
  122. package/src/core/hollowResponseFormat.test.ts +0 -58
  123. package/src/core/launchContext.test.ts +0 -157
  124. package/src/core/models/codexCapabilities.test.ts +0 -45
  125. package/src/core/models/codexModelCapabilities.test.ts +0 -246
  126. package/src/core/models/modelSpecs.test.ts +0 -283
  127. package/src/core/perf/renderDebug.test.ts +0 -230
  128. package/src/core/planStorage.test.ts +0 -143
  129. package/src/core/process/CommandRunner.test.ts +0 -105
  130. package/src/core/projectInstructions.test.ts +0 -50
  131. package/src/core/providerLauncher/launcher.test.ts +0 -238
  132. package/src/core/providerLauncher/registry.test.ts +0 -324
  133. package/src/core/providerLauncher/workspaceConfig.test.ts +0 -638
  134. package/src/core/providerRuntime/anthropic.test.ts +0 -1120
  135. package/src/core/providerRuntime/capabilityProfile.test.ts +0 -311
  136. package/src/core/providerRuntime/contextMetadata.test.ts +0 -468
  137. package/src/core/providerRuntime/gemini.test.ts +0 -437
  138. package/src/core/providerRuntime/lmstudio.test.ts +0 -168
  139. package/src/core/providerRuntime/local.test.ts +0 -787
  140. package/src/core/providerRuntime/registry.test.ts +0 -233
  141. package/src/core/providers/codexJsonStream.test.ts +0 -148
  142. package/src/core/providers/codexSubprocess.test.ts +0 -68
  143. package/src/core/providers/codexTranscript.test.ts +0 -284
  144. package/src/core/terminal/startupClear.test.ts +0 -55
  145. package/src/core/terminal/terminalCapabilities.test.ts +0 -93
  146. package/src/core/terminal/terminalControl.test.ts +0 -75
  147. package/src/core/terminal/terminalSanitize.test.ts +0 -22
  148. package/src/core/terminal/terminalSelection.test.ts +0 -42
  149. package/src/core/terminal/terminalTitle.test.ts +0 -328
  150. package/src/core/updateCheck.test.ts +0 -194
  151. package/src/core/updateCheck.ts +0 -172
  152. package/src/core/workspaceActivity.test.ts +0 -163
  153. package/src/core/workspaceGuard.test.ts +0 -151
  154. package/src/core/workspaceRoot.test.ts +0 -23
  155. package/src/exec.test.ts +0 -13
  156. package/src/headless/execArgs.test.ts +0 -147
  157. package/src/headless/execRunner.test.ts +0 -436
  158. package/src/index.test.tsx +0 -620
  159. package/src/session/appSession.test.ts +0 -897
  160. package/src/session/chatLifecycle.test.ts +0 -64
  161. package/src/session/liveRenderScheduler.test.ts +0 -201
  162. package/src/session/planFlow.test.ts +0 -103
  163. package/src/session/planTranscript.test.ts +0 -65
  164. package/src/session/promptRunSchedule.test.ts +0 -36
  165. package/src/ui/ActivityIndicator.test.tsx +0 -58
  166. package/src/ui/AgentBlock.test.ts +0 -6
  167. package/src/ui/AnimatedStatusText.test.ts +0 -16
  168. package/src/ui/AppShell.test.tsx +0 -1776
  169. package/src/ui/AttachmentImportPanel.test.tsx +0 -204
  170. package/src/ui/BottomComposer.test.ts +0 -674
  171. package/src/ui/CodexLogo.tsx +0 -55
  172. package/src/ui/Markdown.test.ts +0 -157
  173. package/src/ui/ModelPickerProviderScope.test.tsx +0 -411
  174. package/src/ui/ModelPickerScreen.test.tsx +0 -99
  175. package/src/ui/ModelPickerState.test.tsx +0 -151
  176. package/src/ui/ModelReasoningPicker.test.tsx +0 -447
  177. package/src/ui/PlanReviewPanel.test.tsx +0 -267
  178. package/src/ui/PromptCardBorder.test.tsx +0 -161
  179. package/src/ui/ProviderPicker.test.tsx +0 -289
  180. package/src/ui/ProviderShortcut.test.tsx +0 -143
  181. package/src/ui/SettingsPanel.test.tsx +0 -233
  182. package/src/ui/StaticTranscriptItem.tsx +0 -56
  183. package/src/ui/Timeline.test.ts +0 -2067
  184. package/src/ui/TimelineNavigation.test.tsx +0 -201
  185. package/src/ui/TopHeader.test.tsx +0 -254
  186. package/src/ui/TurnGroup.test.tsx +0 -365
  187. package/src/ui/busyStatusAnimation.test.ts +0 -30
  188. package/src/ui/commandNormalize.test.ts +0 -142
  189. package/src/ui/diffRenderer.test.ts +0 -102
  190. package/src/ui/focusFlow.test.tsx +0 -1098
  191. package/src/ui/inputBuffer.test.ts +0 -151
  192. package/src/ui/layout.test.ts +0 -146
  193. package/src/ui/modeDisplay.test.ts +0 -42
  194. package/src/ui/runActivityView.test.ts +0 -89
  195. package/src/ui/runLifecycleView.test.tsx +0 -237
  196. package/src/ui/statusRenderIsolation.test.tsx +0 -654
  197. package/src/ui/terminalAnswerFormat.test.ts +0 -19
  198. package/src/ui/textLayout.test.ts +0 -18
  199. package/src/ui/themeFlow.test.ts +0 -53
  200. package/src/ui/timelineMeasureCache.test.ts +0 -986
  201. /package/src/core/{inputDebug.ts → debug/inputDebug.ts} +0 -0
  202. /package/src/core/{clipboard.ts → shared/clipboard.ts} +0 -0
  203. /package/src/core/{githubDiagnostics.ts → shared/githubDiagnostics.ts} +0 -0
  204. /package/src/core/{projectInstructions.ts → workspace/projectInstructions.ts} +0 -0
  205. /package/src/core/{workspaceActivity.ts → workspace/workspaceActivity.ts} +0 -0
  206. /package/src/core/{workspaceRoot.ts → workspace/workspaceRoot.ts} +0 -0
@@ -1,4 +1,6 @@
1
1
  import { sanitizeTerminalOutput } from "../terminal/terminalSanitize.js";
2
+ import { runAgentLoop, type AgentChatMessage, type AgentChatResponse } from "../agent/loop.js";
3
+ import { parseOpenAiToolCalls } from "../agent/protocol.js";
2
4
  import type { BackendRunHandlers } from "../providers/types.js";
3
5
  import type { ProviderWorkspaceOverride } from "../providerLauncher/types.js";
4
6
  import type {
@@ -456,21 +458,110 @@ function getCachedSelectedModel(config: LocalProviderConfig, routeModel: string)
456
458
  return selectFallbackLocalModel(config, discoveredIds) ?? routeModel;
457
459
  }
458
460
 
459
- function extractNonStreamingText(body: unknown): string {
460
- if (typeof body !== "object" || body === null) return "";
461
- const choices = (body as { choices?: unknown }).choices;
462
- if (!Array.isArray(choices)) return "";
463
- return choices
464
- .map((choice) => {
465
- if (typeof choice !== "object" || choice === null) return "";
466
- const message = (choice as { message?: { content?: unknown }; text?: unknown }).message;
467
- if (typeof message?.content === "string") return message.content;
468
- const text = (choice as { text?: unknown }).text;
469
- return typeof text === "string" ? text : "";
470
- })
471
- .join("")
472
- .trim();
473
- }
461
+ interface LocalResponseDiagnostics {
462
+ choiceCount: number;
463
+ finishReasons: string[];
464
+ recognizedFields: string[];
465
+ topLevelKeys: string[];
466
+ }
467
+
468
+ interface ExtractedLocalResponse extends AgentChatResponse {
469
+ diagnostics: LocalResponseDiagnostics;
470
+ }
471
+
472
+ function textFromContent(value: unknown): string {
473
+ if (typeof value === "string") return value;
474
+ if (Array.isArray(value)) return value.map(textFromContent).join("");
475
+ if (!isRecord(value)) return "";
476
+
477
+ // OpenAI-compatible servers commonly emit content as typed parts, such as
478
+ // { type: "text", text: "..." } or { type: "output_text", text: "..." }.
479
+ if (typeof value.text === "string") return value.text;
480
+ if (typeof value.content === "string" || Array.isArray(value.content)) {
481
+ return textFromContent(value.content);
482
+ }
483
+ if ((value.type === "text" || value.type === "output_text") && typeof value.value === "string") {
484
+ return value.value;
485
+ }
486
+ return "";
487
+ }
488
+
489
+ function messageFieldText(message: Record<string, unknown>, fields: readonly string[], recognizedFields: Set<string>): string {
490
+ for (const field of fields) {
491
+ const text = textFromContent(message[field]);
492
+ if (text.trim()) {
493
+ recognizedFields.add(`message.${field}`);
494
+ return text;
495
+ }
496
+ }
497
+ return "";
498
+ }
499
+
500
+ function extractNonStreamingResponse(body: unknown): ExtractedLocalResponse {
501
+ const topLevelKeys = isRecord(body) ? Object.keys(body).sort() : [];
502
+ const choices = isRecord(body) && Array.isArray(body.choices) ? body.choices : [];
503
+ const recognizedFields = new Set<string>();
504
+ const finishReasons: string[] = [];
505
+ const toolCalls = choices.flatMap((choice) => {
506
+ if (!isRecord(choice)) return [];
507
+ if (typeof choice.finish_reason === "string") finishReasons.push(choice.finish_reason);
508
+ const message = isRecord(choice.message) ? choice.message : null;
509
+ return message ? parseOpenAiToolCalls(message.tool_calls) : [];
510
+ });
511
+
512
+ const content: string[] = [];
513
+ const reasoning: string[] = [];
514
+ for (const choice of choices) {
515
+ if (!isRecord(choice)) continue;
516
+ const message = isRecord(choice.message) ? choice.message : null;
517
+ const messageContent = message
518
+ ? messageFieldText(message, ["content"], recognizedFields)
519
+ : "";
520
+ const legacyText = textFromContent(choice.text);
521
+ if (legacyText.trim()) recognizedFields.add("choice.text");
522
+ const answer = messageContent || legacyText;
523
+ if (answer.trim()) {
524
+ content.push(answer);
525
+ continue;
526
+ }
527
+
528
+ const reasoningText = message
529
+ ? messageFieldText(message, ["reasoning_content", "reasoning", "analysis"], recognizedFields)
530
+ : "";
531
+ const choiceReasoning = !reasoningText
532
+ ? messageFieldText(choice, ["reasoning_content", "reasoning", "analysis"], recognizedFields)
533
+ : "";
534
+ if ((reasoningText || choiceReasoning).trim()) {
535
+ reasoning.push(reasoningText || choiceReasoning);
536
+ }
537
+ }
538
+
539
+ return {
540
+ // Reasoning is a fallback only. A response with both fields should show its
541
+ // final answer, while reasoning-only servers still produce useful text.
542
+ text: (content.length > 0 ? content : reasoning).join("").trim(),
543
+ ...(toolCalls.length > 0 ? { toolCalls } : {}),
544
+ diagnostics: {
545
+ choiceCount: choices.length,
546
+ finishReasons: [...new Set(finishReasons)],
547
+ recognizedFields: [...recognizedFields].sort(),
548
+ topLevelKeys,
549
+ },
550
+ };
551
+ }
552
+
553
+ function emptyResponseError(config: LocalProviderConfig, model: string, diagnostics: LocalResponseDiagnostics): Error {
554
+ const finishReasons = diagnostics.finishReasons.length > 0 ? diagnostics.finishReasons.join(", ") : "none";
555
+ const fields = diagnostics.recognizedFields.length > 0 ? diagnostics.recognizedFields.join(", ") : "none";
556
+ const keys = diagnostics.topLevelKeys.length > 0 ? diagnostics.topLevelKeys.join(", ") : "none";
557
+ return new Error([
558
+ "Local OpenAI-compatible API returned no assistant text.",
559
+ `Endpoint: ${config.baseUrl}/chat/completions`,
560
+ `Model: ${model}`,
561
+ `Response details: choices=${diagnostics.choiceCount}; finish_reason=${finishReasons}; recognized_fields=${fields}; top_level_keys=${keys}.`,
562
+ "Check the local server response format or retry the prompt.",
563
+ ].join("\n"));
564
+ }
474
565
 
475
566
  function parseStreamDelta(line: string): string | null {
476
567
  const trimmed = line.trim();
@@ -521,18 +612,19 @@ async function postLocalChatCompletion(options: {
521
612
  request: ProviderChatRequest;
522
613
  config: LocalProviderConfig;
523
614
  stream: boolean;
615
+ messages?: readonly AgentChatMessage[];
524
616
  fetchImpl: FetchImpl;
525
617
  signal?: AbortSignal;
526
618
  handlers: BackendRunHandlers;
527
619
  capProfile: import("./capabilityProfile.js").ModelCapabilityProfile;
528
- }): Promise<string> {
620
+ }): Promise<AgentChatResponse> {
529
621
  const model = getCachedSelectedModel(options.config, options.request.route.modelId);
530
622
  const includeSystemPrompt = options.capProfile.supportsSystemPrompt !== false;
531
- const messages = [
623
+ const messages: readonly AgentChatMessage[] = options.messages ?? [
532
624
  ...(includeSystemPrompt && options.request.projectInstructions?.content
533
- ? [{ role: "system", content: options.request.projectInstructions.content }]
625
+ ? [{ role: "system" as const, content: options.request.projectInstructions.content }]
534
626
  : []),
535
- { role: "user", content: options.request.prompt },
627
+ { role: "user" as const, content: options.request.prompt },
536
628
  ];
537
629
  const response = await options.fetchImpl(`${options.config.baseUrl}/chat/completions`, {
538
630
  method: "POST",
@@ -560,11 +652,15 @@ async function postLocalChatCompletion(options: {
560
652
  }
561
653
 
562
654
  if (options.stream) {
563
- return readStreamingResponse(response, options.handlers);
655
+ return { text: await readStreamingResponse(response, options.handlers) };
564
656
  }
565
657
 
566
- const parsed = JSON.parse(await response.text()) as unknown;
567
- return extractNonStreamingText(parsed);
658
+ const parsed = JSON.parse(await response.text()) as unknown;
659
+ const extracted = extractNonStreamingResponse(parsed);
660
+ if (!extracted.text.trim() && (extracted.toolCalls?.length ?? 0) === 0) {
661
+ throw emptyResponseError(options.config, model, extracted.diagnostics);
662
+ }
663
+ return extracted;
568
664
  }
569
665
 
570
666
  function isModelNotLoadedError(status: number, body: string): boolean {
@@ -589,42 +685,24 @@ export async function runLocalOpenAiCompatible(
589
685
  providerConfig: request.localConfig ?? configuredOverride,
590
686
  rawMetadata: rawMeta,
591
687
  });
592
- const streamingSupported = capProfile.supportsStreaming !== false;
593
-
594
- if (streamingSupported) {
595
- try {
596
- const streamed = await postLocalChatCompletion({
688
+ const text = await runAgentLoop({
689
+ request,
690
+ handlers,
691
+ includeSystemPrompt: capProfile.supportsSystemPrompt !== false,
692
+ signal: options.signal,
693
+ sendMessages: async (messages) =>
694
+ postLocalChatCompletion({
597
695
  request,
598
696
  config,
599
- stream: true,
697
+ messages,
698
+ stream: false,
600
699
  fetchImpl,
601
700
  signal: options.signal,
602
701
  handlers,
603
702
  capProfile,
604
- });
605
- if (streamed) return streamed;
606
- } catch (error) {
607
- if (options.signal?.aborted) throw error;
608
- handlers.onProgress?.({
609
- id: "local-stream-fallback",
610
- source: "stderr",
611
- text: "Local streaming request failed; retrying without streaming.",
612
- });
613
- }
614
- }
615
-
616
- const text = await postLocalChatCompletion({
617
- request,
618
- config,
619
- stream: false,
620
- fetchImpl,
621
- signal: options.signal,
622
- handlers,
623
- capProfile,
703
+ }),
624
704
  });
625
- if (!text) {
626
- throw new Error("Local OpenAI-compatible API returned no assistant text.");
627
- }
705
+ if (!text) throw new Error("Local OpenAI-compatible API returned no assistant text after tool execution.");
628
706
  handlers.onAssistantDelta?.(text);
629
707
  return text;
630
708
  }
@@ -73,16 +73,20 @@ export const GEMINI_FALLBACK_MODELS: readonly ProviderModel[] = [
73
73
  },
74
74
  ] as const;
75
75
 
76
+ function fallbackModelDescription(family: string): string {
77
+ return `Claude ${family} alias — version unknown because model discovery is unavailable`;
78
+ }
79
+
76
80
  export const ANTHROPIC_FALLBACK_MODELS: readonly ProviderModel[] = [
77
81
  {
78
82
  id: "opus",
79
83
  modelId: "opus",
80
- label: "Opus 4.7",
81
- description: "Claude Opus 4.7 - Fallback defaults",
84
+ label: "Claude Opus (version unknown)",
85
+ description: fallbackModelDescription("Opus"),
82
86
  defaultReasoningLevel: "xhigh",
83
87
  supportedReasoningLevels: getClaudeCodeEffortLevels(["low", "medium", "high", "xhigh", "max"]),
84
88
  source: "fallback",
85
- canonicalId: "claude-opus-4-7",
89
+ canonicalId: "opus",
86
90
  family: "opus",
87
91
  effortSource: "fallback",
88
92
  effortVerified: false,
@@ -90,12 +94,12 @@ export const ANTHROPIC_FALLBACK_MODELS: readonly ProviderModel[] = [
90
94
  {
91
95
  id: "sonnet",
92
96
  modelId: "sonnet",
93
- label: "Sonnet 4.6",
94
- description: "Claude Sonnet 4.6 - Fallback defaults",
97
+ label: "Claude Sonnet (version unknown)",
98
+ description: fallbackModelDescription("Sonnet"),
95
99
  defaultReasoningLevel: "high",
96
100
  supportedReasoningLevels: getClaudeCodeEffortLevels(["low", "medium", "high", "max"]),
97
101
  source: "fallback",
98
- canonicalId: "claude-sonnet-4-6",
102
+ canonicalId: "sonnet",
99
103
  family: "sonnet",
100
104
  effortSource: "fallback",
101
105
  effortVerified: false,
@@ -103,20 +107,27 @@ export const ANTHROPIC_FALLBACK_MODELS: readonly ProviderModel[] = [
103
107
  {
104
108
  id: "haiku",
105
109
  modelId: "haiku",
106
- label: "Haiku 4.5",
107
- description: "Claude Haiku 4.5 - Fallback defaults; effort metadata unverified",
110
+ label: "Claude Haiku (version unknown)",
111
+ description: fallbackModelDescription("Haiku"),
108
112
  defaultReasoningLevel: "medium",
109
113
  supportedReasoningLevels: getClaudeCodeEffortLevels(["low", "medium", "high"]),
110
114
  source: "fallback",
111
- canonicalId: "claude-haiku-4-5",
115
+ canonicalId: "haiku",
112
116
  family: "haiku",
113
117
  effortSource: "fallback",
114
118
  effortVerified: false,
115
119
  },
116
- ] as const;
120
+ ];
117
121
 
118
122
  function isRuntimeSource(source: ProviderModel["source"]): boolean {
119
- return source === "discovered" || source === "claude-code" || source === "settings" || source === "config";
123
+ return source === "discovered"
124
+ || source === "claude-code"
125
+ || source === "claude-code-command"
126
+ || source === "claude-code-package"
127
+ || source === "claude-code-cache"
128
+ || source === "claude-code-config"
129
+ || source === "settings"
130
+ || source === "config";
120
131
  }
121
132
 
122
133
  export function providerModelsToCodexCapabilities(
@@ -1,9 +1,10 @@
1
1
  import { codexSubprocessProvider } from "../providers/codexSubprocess.js";
2
2
  import type { BackendRunHandlers } from "../providers/types.js";
3
3
  import type { ProviderId, ProviderActiveRoute, ProviderWorkspaceOverride } from "../providerLauncher/types.js";
4
- import { anthropicRuntime } from "./anthropic.js";
5
- import { geminiRuntime } from "./gemini.js";
6
- import { localRuntime } from "./local.js";
4
+ import { anthropicRuntime } from "./anthropic.js";
5
+ import { geminiRuntime } from "./gemini.js";
6
+ import { localRuntime } from "./local.js";
7
+ import { antigravityRuntime, ANTIGRAVITY_DEFAULT_MODEL_ID, migrateAntigravityLegacyModelId } from "./antigravity.js";
7
8
  import {
8
9
  ANTHROPIC_FALLBACK_MODELS,
9
10
  GEMINI_DEFAULT_MODEL_ID,
@@ -71,10 +72,11 @@ function unavailableRuntime(providerId: ProviderId, label: string): ProviderRunt
71
72
 
72
73
  const PROVIDER_RUNTIMES: Record<ProviderId, ProviderRuntime> = {
73
74
  openai: openAiRuntime,
74
- anthropic: anthropicRuntime,
75
- google: geminiRuntime,
76
- local: localRuntime,
77
- };
75
+ anthropic: anthropicRuntime,
76
+ google: geminiRuntime,
77
+ local: localRuntime,
78
+ antigravity: antigravityRuntime,
79
+ };
78
80
 
79
81
  export function getProviderRuntime(providerId: ProviderId): ProviderRuntime {
80
82
  return PROVIDER_RUNTIMES[providerId];
@@ -100,11 +102,12 @@ export function discoverProviderModels(providerId: ProviderId): ProviderModelDis
100
102
 
101
103
  export async function validateProviderRouteActivation(options: {
102
104
  route: ProviderRoute;
103
- workspaceRoot: string;
104
- geminiCommandPath?: string | null;
105
- claudeCommandPath?: string | null;
106
- localConfig?: ProviderWorkspaceOverride | null;
107
- }): Promise<ProviderRouteValidationResult> {
105
+ workspaceRoot: string;
106
+ geminiCommandPath?: string | null;
107
+ claudeCommandPath?: string | null;
108
+ antigravityCommandPath?: string | null;
109
+ localConfig?: ProviderWorkspaceOverride | null;
110
+ }): Promise<ProviderRouteValidationResult> {
108
111
  const runtime = getProviderRuntime(options.route.providerId);
109
112
  if (!runtime.routeAvailable) {
110
113
  return {
@@ -154,7 +157,7 @@ export function resolveActiveProviderRoute(options: {
154
157
  currentReasoning: string;
155
158
  }): ActiveProviderRoute {
156
159
  const configuredRoute = options.workspaceConfigActiveRoute;
157
- if (configuredRoute && isProviderRoutableInCodexa(configuredRoute.providerId)) {
160
+ if (configuredRoute && configuredRoute.providerId !== "google" && isProviderRoutableInCodexa(configuredRoute.providerId)) {
158
161
  const route: ActiveProviderRoute = {
159
162
  providerId: configuredRoute.providerId,
160
163
  modelId: configuredRoute.modelId,
@@ -163,19 +166,36 @@ export function resolveActiveProviderRoute(options: {
163
166
  ...(configuredRoute.modelSelection ? { modelSelection: configuredRoute.modelSelection } : {}),
164
167
  };
165
168
 
166
- if (route.providerId === "google" && route.modelSelection) {
167
- route.modelId = resolveGeminiModelId(route.modelSelection);
168
- } else if (route.providerId === "google") {
169
- route.modelId = normalizeGeminiModelId(route.modelId);
170
- } else if (route.providerId === "local") {
171
- const discovery = discoverProviderModels("local");
172
- const selectedModel = typeof discovery.diagnostics?.selectedModel === "string"
169
+ if (route.providerId === "google" && route.modelSelection) {
170
+ route.modelId = resolveGeminiModelId(route.modelSelection);
171
+ } else if (route.providerId === "google") {
172
+ route.modelId = normalizeGeminiModelId(route.modelId);
173
+ } else if (route.providerId === "anthropic") {
174
+ const discovery = discoverProviderModels("anthropic");
175
+ const stillAvailable = discovery.models.some((model) =>
176
+ model.modelId === route.modelId ||
177
+ model.id === route.modelId ||
178
+ model.canonicalId === route.modelId
179
+ );
180
+ const hasNonFallbackModels = discovery.models.some((model) => model.source !== "fallback");
181
+ if (discovery.status === "ready" && hasNonFallbackModels && discovery.models.length > 0 && !stillAvailable) {
182
+ route.modelId = discovery.models[0]!.modelId;
183
+ }
184
+ } else if (route.providerId === "local") {
185
+ const discovery = discoverProviderModels("local");
186
+ const selectedModel = typeof discovery.diagnostics?.selectedModel === "string"
173
187
  ? discovery.diagnostics.selectedModel.trim()
174
188
  : "";
175
- if (discovery.status === "ready" && selectedModel) {
176
- route.modelId = selectedModel;
177
- }
178
- }
189
+ if (discovery.status === "ready" && selectedModel) {
190
+ route.modelId = selectedModel;
191
+ }
192
+ } else if (route.providerId === "antigravity") {
193
+ const migrated = migrateAntigravityLegacyModelId(route.modelId);
194
+ route.modelId = migrated.modelId;
195
+ if (!route.reasoning && migrated.reasoning) {
196
+ route.reasoning = migrated.reasoning;
197
+ }
198
+ }
179
199
 
180
200
  return route;
181
201
  }
@@ -190,14 +210,21 @@ export function resolveActiveProviderRoute(options: {
190
210
 
191
211
  export function getDefaultRouteModel(providerId: ProviderId, currentOpenAiModel: string): string {
192
212
  if (providerId === "anthropic") {
193
- return ANTHROPIC_FALLBACK_MODELS[0]?.modelId ?? "claude-sonnet-4-20250514";
213
+ const discovered = discoverProviderModels("anthropic");
214
+ if (discovered.status === "ready" && discovered.models.length > 0) {
215
+ return discovered.models[0].modelId;
216
+ }
217
+ return ANTHROPIC_FALLBACK_MODELS[0]?.modelId ?? "sonnet";
194
218
  }
195
219
  if (providerId === "google") {
196
220
  return GEMINI_FALLBACK_MODELS[0]?.modelId ?? GEMINI_DEFAULT_MODEL_ID;
197
221
  }
198
- if (providerId === "local") {
199
- const discovery = discoverProviderModels("local");
200
- return discovery.models[0]?.modelId ?? "Local default";
201
- }
202
- return currentOpenAiModel;
203
- }
222
+ if (providerId === "local") {
223
+ const discovery = discoverProviderModels("local");
224
+ return discovery.models[0]?.modelId ?? "Local default";
225
+ }
226
+ if (providerId === "antigravity") {
227
+ return ANTIGRAVITY_DEFAULT_MODEL_ID;
228
+ }
229
+ return currentOpenAiModel;
230
+ }
@@ -1,5 +1,5 @@
1
1
  import type { ReasoningEffortCapability } from "../models/codexModelCapabilities.js";
2
- import type { ProjectInstructions } from "../projectInstructions.js";
2
+ import type { ProjectInstructions } from "../workspace/projectInstructions.js";
3
3
  import type { BackendRunHandlers } from "../providers/types.js";
4
4
  import type { ResolvedRuntimeConfig } from "../../config/runtimeConfig.js";
5
5
  export type { ResolvedRuntimeConfig };
@@ -7,10 +7,11 @@ import type { ProviderId } from "../providerLauncher/types.js";
7
7
  import type { ProviderWorkspaceOverride } from "../providerLauncher/types.js";
8
8
 
9
9
  export type ProviderBackendKind =
10
- | "codex-cli-auth"
11
- | "gemini-cli-auth"
12
- | "claude-code-auth"
13
- | "openai-api-key"
10
+ | "codex-cli-auth"
11
+ | "gemini-cli-auth"
12
+ | "claude-code-auth"
13
+ | "antigravity-cli-auth"
14
+ | "openai-api-key"
14
15
  | "gemini-api-key"
15
16
  | "anthropic-api-key"
16
17
  | "local-openai-compatible"
@@ -23,10 +24,13 @@ export interface ProviderModel {
23
24
  description: string | null;
24
25
  defaultReasoningLevel: string | null;
25
26
  supportedReasoningLevels: readonly ReasoningEffortCapability[] | null;
26
- source?: "discovered" | "claude-code" | "settings" | "config" | "fallback";
27
- canonicalId?: string;
28
- family?: string;
29
- effortSource?: "claude-code" | "settings" | "config" | "fallback";
27
+ source?: "discovered" | "claude-code" | "claude-code-command" | "claude-code-package" | "claude-code-cache" | "claude-code-config" | "settings" | "config" | "fallback";
28
+ canonicalId?: string;
29
+ family?: string;
30
+ version?: string;
31
+ isFallback?: boolean;
32
+ discoveryKind?: "models" | "aliases";
33
+ effortSource?: "claude-code" | "claude-code-command" | "claude-code-package" | "claude-code-cache" | "claude-code-config" | "settings" | "config" | "fallback";
30
34
  effortVerified?: boolean;
31
35
  raw?: unknown;
32
36
  }
@@ -58,11 +62,12 @@ export type ActiveProviderRoute = ProviderRoute;
58
62
 
59
63
  export interface ProviderRouteValidationRequest {
60
64
  route: ProviderRoute;
61
- workspaceRoot: string;
62
- geminiCommandPath?: string | null;
63
- claudeCommandPath?: string | null;
64
- localConfig?: ProviderWorkspaceOverride | null;
65
- }
65
+ workspaceRoot: string;
66
+ geminiCommandPath?: string | null;
67
+ claudeCommandPath?: string | null;
68
+ antigravityCommandPath?: string | null;
69
+ localConfig?: ProviderWorkspaceOverride | null;
70
+ }
66
71
 
67
72
  export interface ProviderRouteValidationResult {
68
73
  status: "ready" | "not-configured";
@@ -1,8 +1,8 @@
1
1
  import { spawn } from "child_process";
2
2
  import { formatCodexLaunchError, spawnCodexProcess } from "../executables/codexExecutable.js";
3
- import { prepareCodexExecLaunch } from "../codexLaunch.js";
3
+ import { prepareCodexExecLaunch } from "../codex/codexLaunch.js";
4
4
  import * as perf from "../perf/profiler.js";
5
- import { buildCodexPrompt } from "../codexPrompt.js";
5
+ import { buildCodexPrompt } from "../codex/codexPrompt.js";
6
6
  import { createTerminalTitleSequenceStripper } from "../terminal/terminalTitle.js";
7
7
  import { createCodexJsonStreamParser } from "./codexJsonStream.js";
8
8
  import {
@@ -1,6 +1,6 @@
1
1
  import type { AvailableBackend } from "../../config/settings.js";
2
2
  import type { ResolvedRuntimeConfig } from "../../config/runtimeConfig.js";
3
- import type { ProjectInstructions } from "../projectInstructions.js";
3
+ import type { ProjectInstructions } from "../workspace/projectInstructions.js";
4
4
  import type { RunProgressSource, RunToolActivity } from "../../session/types.js";
5
5
 
6
6
  export interface BackendProgressUpdate {
@@ -1,5 +1,10 @@
1
- import { access, copyFile, mkdir } from "node:fs/promises";
1
+ import { access, copyFile, mkdir, stat } from "node:fs/promises";
2
+ import { existsSync } from "node:fs";
2
3
  import path from "node:path";
4
+ import {
5
+ isSkippedExternalDependencyPath,
6
+ normalizeDiagnosticPath,
7
+ } from "../workspace/workspaceGuard.js";
3
8
 
4
9
  export const IMAGE_EXTENSIONS = new Set([
5
10
  ".png",
@@ -44,10 +49,28 @@ export async function resolveAttachmentDestPath(
44
49
  export async function importExternalFile(
45
50
  srcPath: string,
46
51
  attachmentsDir: string,
47
- ): Promise<string> {
52
+ ): Promise<string | null> {
53
+ const normalized = normalizeDiagnosticPath(srcPath);
54
+
55
+ if (isSkippedExternalDependencyPath(normalized)) {
56
+ return null;
57
+ }
58
+
59
+ try {
60
+ if (!existsSync(normalized)) {
61
+ return null;
62
+ }
63
+ const s = await stat(normalized);
64
+ if (!s.isFile()) {
65
+ return null;
66
+ }
67
+ } catch {
68
+ return null;
69
+ }
70
+
48
71
  await mkdir(attachmentsDir, { recursive: true });
49
- const destPath = await resolveAttachmentDestPath(srcPath, attachmentsDir);
50
- await copyFile(srcPath, destPath);
72
+ const destPath = await resolveAttachmentDestPath(normalized, attachmentsDir);
73
+ await copyFile(normalized, destPath);
51
74
  return destPath;
52
75
  }
53
76
 
@@ -1,4 +1,4 @@
1
- import type { RunToolActivity } from "../session/types.js";
1
+ import type { RunToolActivity } from "../../session/types.js";
2
2
 
3
3
  const DELETE_COMMAND_PATTERN =
4
4
  /(?:^|[\s;&|])(?:remove-item|rm|rmdir|del|erase|unlink)\b/i;
@@ -1,4 +1,4 @@
1
- import type { HollowResponseResult } from "./codexPrompt.js";
1
+ import type { HollowResponseResult } from "../codex/codexPrompt.js";
2
2
 
3
3
  const MESSAGES: Record<string, [string, string]> = {
4
4
  greeting: [