@@ -216,6 +216,7 @@ def configure_shared_state(
216216 force_login : bool = False ,
217217 use_pat : bool | None = None ,
218218 skip_model_discovery : bool = False ,
219+ skip_preflight : bool = False ,
219220) -> dict :
220221 """Log into Databricks, enforce AI Gateway v2, fetch model lists, persist state.
221222
@@ -229,13 +230,61 @@ def configure_shared_state(
229230 ``--profile`` to every CLI invocation so ambiguous `~/.databrickscfg`
230231 entries (e.g. DEFAULT and a named profile both pointing at the same host)
231232 don't error out. If ``None``, we resolve it from the host after login.
233+ If skip_preflight is True, skip the entire preflight block below — auth
234+ validation, the AI Gateway probe, and model discovery — trusting a prior
235+ ``ucode configure``. The PAT/bearer is already exported (``apply_pat_environment``
236+ in ``_launch_tool``) and the gateway was verified by that earlier configure.
237+ Only the local profile resolution and the shared state assembly still run;
238+ the saved model lists are preserved.
232239 """
233240 workspace = normalize_workspace_url (workspace )
234241 prior_state = load_state ()
235242 previous_workspace = prior_state .get ("workspace" )
236243 if use_pat is None :
237244 use_pat = bool (prior_state .get ("use_pat" )) and previous_workspace == workspace
238245 fetch_all = tools is None
246+
247+ # Assemble the shared workspace state that doesn't depend on model discovery:
248+ # workspace, profile, auth mode, base URLs. `profile` may still be None here;
249+ # each path below resolves it once, where a host->profile lookup is reliable
250+ # (the skip branch trusts the prior configure; the preflight resolves after
251+ # login). --skip-preflight persists exactly this and returns, trusting a prior
252+ # `ucode configure` — it already validated auth + the AI Gateway and saved the
253+ # model lists (carried over by load_state, left untouched).
254+ state = load_state ()
255+ state ["workspace" ] = workspace
256+ if profile :
257+ state ["profile" ] = profile
258+ else :
259+ state .pop ("profile" , None )
260+ # UC discovery is now always-on; drop any flag persisted by older versions.
261+ state .pop ("uc_enabled" , None )
262+ # Persist the auth mode so launches rebuild the same (PAT-based) agent
263+ # auth command; an explicit re-configure without --use-pat clears it.
264+ if use_pat :
265+ state ["use_pat" ] = True
266+ else :
267+ state .pop ("use_pat" , None )
268+ state ["base_urls" ] = build_shared_base_urls (workspace )
269+
270+ if skip_preflight :
271+ # A prior `ucode configure` created the profile; resolve it locally (no
272+ # login needed) and persist it so launches disambiguate.
273+ if profile is None :
274+ profile = find_profile_name_for_host (workspace )
275+ if profile :
276+ state ["profile" ] = profile
277+ save_state (state )
278+ # Scrub MCP entries ucode wrote for a previous workspace.
279+ if previous_workspace and previous_workspace != workspace :
280+ purge_cross_workspace_mcp_residue (state , workspace )
281+ # Diagnostic reasons are transient (attached after save_state so they
282+ # don't land on disk). No discovery ran, so there is nothing to report.
283+ state ["_discovery_reasons" ] = {"claude" : None , "gemini" : None , "codex" : None , "oss" : None }
284+ return state
285+
286+ # ── Preflight (bypassed above under --skip-preflight): validate Databricks
287+ # auth + the AI Gateway, then discover the available models. ──
239288 if use_pat :
240289 if not profile :
241290 raise RuntimeError (
@@ -260,9 +309,11 @@ def configure_shared_state(
260309 else :
261310 ensure_databricks_auth (workspace , profile )
262311 # After login the profile exists in ~/.databrickscfg, so a host->profile
263- # lookup is reliable. Persist it so subsequent CLI calls disambiguate .
312+ # lookup is reliable even when it returned nothing above .
264313 if profile is None :
265314 profile = find_profile_name_for_host (workspace )
315+ if profile :
316+ state ["profile" ] = profile
266317 with spinner ("Verifying Unity AI Gateway..." ):
267318 token = get_databricks_token (workspace , profile )
268319 ensure_ai_gateway_v2 (workspace , token )
@@ -283,6 +334,7 @@ def configure_shared_state(
283334 gemini_models = []
284335 codex_models = []
285336 oss_models = []
337+ opencode_models : dict [str , list [str ]] = {}
286338 web_search_model : str | None = None
287339 if skip_model_discovery :
288340 # Provider mode: the agent routes through a Model Provider Service and
@@ -295,10 +347,11 @@ def configure_shared_state(
295347 if ws_models :
296348 web_search_model = ws_models [0 ]
297349 else :
298- # UC-first, best-effort: one UC model-services call yields all families as
299- # `system.ai.<model-name>` ids, bucketed by name. If a family comes back
300- # empty (workspace without UC model-services, or the listing failed), fall
301- # back to the per-family AI Gateway listing for that family only.
350+ # UC-first, best-effort: one UC model-services call yields all families
351+ # as `system.ai.<model-name>` ids, bucketed by name. If a family comes
352+ # back empty (workspace without UC model-services, or the listing
353+ # failed), fall back to the per-family AI Gateway listing for that
354+ # family only.
302355 with spinner ("Fetching available models..." ):
303356 ms_claude , ms_codex , ms_gemini , ms_oss , ms_reason = discover_model_services (
304357 workspace , token
@@ -317,30 +370,13 @@ def configure_shared_state(
317370 codex_models , codex_reason = discover_codex_models (workspace , token )
318371 if want_oss :
319372 oss_models , oss_reason = ms_oss , ms_reason
320- opencode_models : dict [str , list [str ]] = {}
321- if claude_models :
322- opencode_models ["anthropic" ] = list (claude_models .values ())
323- if gemini_models :
324- opencode_models ["gemini" ] = gemini_models
325- if oss_models :
326- opencode_models ["oss" ] = oss_models
327-
328- # Merge into existing workspace state so prior tool configs are preserved.
329- state = load_state ()
330- state ["workspace" ] = workspace
331- if profile :
332- state ["profile" ] = profile
333- else :
334- state .pop ("profile" , None )
335- # UC discovery is now always-on; drop any flag persisted by older versions.
336- state .pop ("uc_enabled" , None )
337- # Persist the auth mode so launches rebuild the same (PAT-based) agent
338- # auth command; an explicit re-configure without --use-pat clears it.
339- if use_pat :
340- state ["use_pat" ] = True
341- else :
342- state .pop ("use_pat" , None )
343- state ["base_urls" ] = build_shared_base_urls (workspace )
373+ if claude_models :
374+ opencode_models ["anthropic" ] = list (claude_models .values ())
375+ if gemini_models :
376+ opencode_models ["gemini" ] = gemini_models
377+ if oss_models :
378+ opencode_models ["oss" ] = oss_models
379+
344380 if skip_model_discovery :
345381 # Don't clobber any previously-discovered Databricks model lists; provider
346382 # mode just doesn't refresh or use them. Persist the web-search model so
@@ -822,7 +858,12 @@ def _auto_configure_tool(tool: str) -> None:
822858 raise RuntimeError (f"{ spec ['display' ]} validation failed — config reverted." )
823859
824860
825- def _launch_tool (tool_name : str , ctx : typer .Context , provider : str | None = None ) -> None :
861+ def _launch_tool (
862+ tool_name : str ,
863+ ctx : typer .Context ,
864+ provider : str | None = None ,
865+ skip_preflight : bool = False ,
866+ ) -> None :
826867 try :
827868 tool = normalize_tool (tool_name )
828869 existing = load_state ()
@@ -860,6 +901,7 @@ def _launch_tool(tool_name: str, ctx: typer.Context, provider: str | None = None
860901 profile = state .get ("profile" ),
861902 tools = [tool ],
862903 skip_model_discovery = bool (provider ),
904+ skip_preflight = skip_preflight ,
863905 )
864906 if provider :
865907 # Routing through a Model Provider Service pins no Databricks model;
@@ -892,6 +934,20 @@ def _launch_tool(tool_name: str, ctx: typer.Context, provider: str | None = None
892934 raise typer .Exit (130 ) from None
893935
894936
937+ # Launch-only escape hatch for managed/headless launchers (e.g. omnigent) that
938+ # have already run `ucode configure`: skip the ~5-10s per-launch auth + AI
939+ # Gateway re-validation. Distinct from the configure-only `--skip-validate`,
940+ # which skips the model smoke test.
941+ SkipPreflightOption = Annotated [
942+ bool ,
943+ typer .Option (
944+ "--skip-preflight" ,
945+ help = "Skip the per-launch Databricks auth + AI Gateway re-validation, trusting a "
946+ "prior `ucode configure`." ,
947+ ),
948+ ]
949+
950+
895951@app .command ("codex" , context_settings = {"allow_extra_args" : True , "ignore_unknown_options" : True })
896952def codex_cmd (
897953 ctx : typer .Context ,
@@ -904,9 +960,10 @@ def codex_cmd(
904960 "before any `--` separator." ,
905961 ),
906962 ] = None ,
963+ skip_preflight : SkipPreflightOption = False ,
907964) -> None :
908965 """Launch Codex via Databricks."""
909- _launch_tool ("codex" , ctx , provider = provider )
966+ _launch_tool ("codex" , ctx , provider = provider , skip_preflight = skip_preflight )
910967
911968
912969@app .command ("claude" , context_settings = {"allow_extra_args" : True , "ignore_unknown_options" : True })
@@ -921,35 +978,36 @@ def claude_cmd(
921978 "before any `--` separator." ,
922979 ),
923980 ] = None ,
981+ skip_preflight : SkipPreflightOption = False ,
924982) -> None :
925983 """Launch Claude Code via Databricks."""
926- _launch_tool ("claude" , ctx , provider = provider )
984+ _launch_tool ("claude" , ctx , provider = provider , skip_preflight = skip_preflight )
927985
928986
929987@app .command ("gemini" , context_settings = {"allow_extra_args" : True , "ignore_unknown_options" : True })
930- def gemini_cmd (ctx : typer .Context ) -> None :
988+ def gemini_cmd (ctx : typer .Context , skip_preflight : SkipPreflightOption = False ) -> None :
931989 """Launch Gemini CLI via Databricks."""
932- _launch_tool ("gemini" , ctx )
990+ _launch_tool ("gemini" , ctx , skip_preflight = skip_preflight )
933991
934992
935993@app .command (
936994 "opencode" , context_settings = {"allow_extra_args" : True , "ignore_unknown_options" : True }
937995)
938- def opencode_cmd (ctx : typer .Context ) -> None :
996+ def opencode_cmd (ctx : typer .Context , skip_preflight : SkipPreflightOption = False ) -> None :
939997 """Launch OpenCode via Databricks."""
940- _launch_tool ("opencode" , ctx )
998+ _launch_tool ("opencode" , ctx , skip_preflight = skip_preflight )
941999
9421000
9431001@app .command ("copilot" , context_settings = {"allow_extra_args" : True , "ignore_unknown_options" : True })
944- def copilot_cmd (ctx : typer .Context ) -> None :
1002+ def copilot_cmd (ctx : typer .Context , skip_preflight : SkipPreflightOption = False ) -> None :
9451003 """Launch GitHub Copilot CLI via Databricks."""
946- _launch_tool ("copilot" , ctx )
1004+ _launch_tool ("copilot" , ctx , skip_preflight = skip_preflight )
9471005
9481006
9491007@app .command ("pi" , context_settings = {"allow_extra_args" : True , "ignore_unknown_options" : True })
950- def pi_cmd (ctx : typer .Context ) -> None :
1008+ def pi_cmd (ctx : typer .Context , skip_preflight : SkipPreflightOption = False ) -> None :
9511009 """Launch Pi coding agent via Databricks."""
952- _launch_tool ("pi" , ctx )
1010+ _launch_tool ("pi" , ctx , skip_preflight = skip_preflight )
9531011
9541012
9551013@configure_app .callback (invoke_without_command = True )
0 commit comments