@@ -63,6 +63,7 @@ import { createAgentToolset, type AgentToolset, type OperatorResult } from "../a
6363import { createAgentWithLiveToolDispatch } from "../agent/live-tool-dispatch.js" ;
6464import { liveTelemetry } from "../telemetry/singleton.js" ;
6565import { createTurnObserver } from "../telemetry/ai-observability.js" ;
66+ import { terminalProviderFailureMessage } from "../inference-error-message.js" ;
6667import { collectToolPlugins , resolveToolPlugins } from "../plugins/tool-plugins.js" ;
6768import {
6869 expandExistingPluginMembers ,
@@ -116,6 +117,32 @@ export function formatCaughtError(err: unknown): string {
116117 return err instanceof Error ? err . message : String ( err ) ;
117118}
118119
120+ const SELECTED_PROVIDER_FAILURE = "SelectedProviderFailure" ;
121+
122+ export async function refreshSelectedProviderCredential < T > ( refresh : ( ) => Promise < T > ) : Promise < T > {
123+ try {
124+ return await refresh ( ) ;
125+ } catch ( cause ) {
126+ const error = new Error ( formatCaughtError ( cause ) , { cause } ) ;
127+ error . name = SELECTED_PROVIDER_FAILURE ;
128+ throw error ;
129+ }
130+ }
131+
132+ export function execUserFailureMessage (
133+ config : Config ,
134+ err : unknown ,
135+ inferenceStarted : boolean ,
136+ ) : string {
137+ if ( inferenceStarted || ( err instanceof Error && err . name === SELECTED_PROVIDER_FAILURE ) ) {
138+ return terminalProviderFailureMessage (
139+ config . providerName ,
140+ config . settings ?. providers [ config . providerName ] ?. name ,
141+ ) ;
142+ }
143+ return formatCaughtError ( err ) ;
144+ }
145+
119146/**
120147 * Exec-primary director overlay. Omit / skywalker keep the product default
121148 * (`loadSessionChatPrompt` + advertised session tools). Any other closed-fleet
@@ -252,6 +279,7 @@ export async function runExec(config: Config): Promise<ExecResult> {
252279 let finalized = false ;
253280 let turnsUsed = 0 ;
254281 let runSink : RunSink | null = null ;
282+ let inferenceStarted = false ;
255283
256284 const persist = async (
257285 status : "running" | "done" | "failed" | "cancelled" ,
@@ -605,15 +633,19 @@ export async function runExec(config: Config): Promise<ExecResult> {
605633
606634 // Refresh OAuth tokens before first inference when starting on codex/xai.
607635 if ( initialCodexProfile !== undefined ) {
608- const { access } = await getValidCodexToken ( initialCodexProfile ) ;
636+ const { access } = await refreshSelectedProviderCredential ( ( ) =>
637+ getValidCodexToken ( initialCodexProfile ) ,
638+ ) ;
609639 liveSource = { ...liveSource , apiKey : access } ;
610640 liveSubAgentProvider . current = {
611641 ...liveSubAgentProvider . current ,
612642 apiKey : access ,
613643 } ;
614644 }
615645 if ( initialXaiProfile !== undefined ) {
616- const { access } = await getValidXaiToken ( initialXaiProfile ) ;
646+ const { access } = await refreshSelectedProviderCredential ( ( ) =>
647+ getValidXaiToken ( initialXaiProfile ) ,
648+ ) ;
617649 liveSource = { ...liveSource , apiKey : access } ;
618650 liveSubAgentProvider . current = {
619651 ...liveSubAgentProvider . current ,
@@ -739,6 +771,7 @@ export async function runExec(config: Config): Promise<ExecResult> {
739771 let runError : string | undefined ;
740772 let sinkStatus : ReturnType < typeof liveSink . getStatus > = "cancelled" ;
741773 try {
774+ inferenceStarted = true ;
742775 // Final OAuth refresh immediately before send (token may have aged during MCP).
743776 if ( initialCodexProfile !== undefined ) {
744777 const { access } = await getValidCodexToken ( initialCodexProfile ) ;
@@ -835,17 +868,24 @@ export async function runExec(config: Config): Promise<ExecResult> {
835868 } ) ;
836869
837870 if ( ! sendCompleted || runError !== undefined || summaryStatus === "failed" ) {
838- const message =
871+ const diagnosticMessage =
839872 runError ??
840873 ( summaryStatus === "cancelled" ? "run cancelled before completion" : "run failed" ) ;
841- stderr . write ( `Error: ${ message } \n` ) ;
874+ const userMessage =
875+ summaryStatus === "failed"
876+ ? terminalProviderFailureMessage (
877+ config . providerName ,
878+ config . settings ?. providers [ config . providerName ] ?. name ,
879+ )
880+ : diagnosticMessage ;
881+ stderr . write ( `Error: ${ userMessage } \n` ) ;
842882 const persistStatus = summaryStatus === "cancelled" ? "cancelled" : "failed" ;
843- await persist ( persistStatus , { error : message } ) ;
883+ await persist ( persistStatus , { error : diagnosticMessage } ) ;
844884 return {
845885 exitCode : 1 ,
846886 sessionId,
847887 text : textOut ,
848- error : message ,
888+ error : userMessage ,
849889 status : summaryStatus ,
850890 durationMs : finishedAt - startedAt ,
851891 turnsUsed : runSink . getTurnCount ( ) ,
@@ -870,15 +910,16 @@ export async function runExec(config: Config): Promise<ExecResult> {
870910 model : config . model ,
871911 } ;
872912 } catch ( err ) {
873- const message = err instanceof Error ? err . message : String ( err ) ;
874- logger . error ( "exec failed: {error}" , { error : message } ) ;
875- stderr . write ( `Error: ${ message } \n` ) ;
876- await persist ( "failed" , { error : message } ) ;
913+ const diagnosticMessage = formatCaughtError ( err ) ;
914+ logger . error ( "exec failed: {error}" , { error : diagnosticMessage } ) ;
915+ const userMessage = execUserFailureMessage ( config , err , inferenceStarted ) ;
916+ stderr . write ( `Error: ${ userMessage } \n` ) ;
917+ await persist ( "failed" , { error : diagnosticMessage } ) ;
877918 return {
878919 exitCode : 1 ,
879920 sessionId,
880921 text : textOut ,
881- error : message ,
922+ error : userMessage ,
882923 status : "failed" ,
883924 durationMs : Date . now ( ) - startedAt ,
884925 turnsUsed : runSink ?. getTurnCount ( ) ?? turnsUsed ,
0 commit comments